StrVec and String Class Design(C++Primer 5th)

Quoted C++ Primer 5th Chapter 13.5 Classes That Management Dynamic Memory(P.524)

Design a simplification of the library vector class

Introduction:

     Recall that the vector class stores its elements in contiguous storage. To obtain acceptable performance, vector preallocates enough storage to hold more elements  than are needed (§ 9.4, p. 355). Each vector member that adds elements checks  whether there is space available for another element. If so, the member constructs an object in the next available spot. If there isn’t space left, then the vector is reallocated: The vector obtains new space, moves the existing elements into that space, frees the old space, and adds the new element.

    As an example, we’ll implement a simplification of the library vector class. Among the simplifications we’ll make is that our class will not be a template. Instead, our class will hold strings. Thus, we’ll call our class StrVec

    We’ll use a similar strategy in our StrVec class. We’ll use anallocator to obtain raw memory (§ 12.2.2, p. 481). Because the memory an allocator allocates is unconstructed, we’ll use the allocator’s construct member to create objects in that space when we need to add an element. Similarly, when we remove an element, we’ll use the destroy member to destroy the element.
 Each StrVec will have three pointers into the space it uses for its elements:
 elements:  which points to the first element in the allocated memory
 first_free which points just after the last actual element
 cap:           which points just past the end of the allocated memory

In addition to these pointers, StrVec will have a  member named alloc that is an allocator<string>. The alloc member will allocate the memory used by a StrVec. Our class will also have four utility functions:
1、alloc_n_copy will allocate space and copy a given range of elements.
2、free will destroy the constructed elements and deallocate the space.
3、chk_n_alloc will ensure that there is room to add at least one more element to the StrVec. If there isn’t room for another element, chk_n_alloc will call reallocate to get more space.
4、reallocate will reallocate the StrVec when it runs out of space


StrVec Class Definition

#include <memory>
#include <string>
#include <initializer_list>

#ifndef _MSC_VER
#define NOEXCEPT noexcept
#else
#define NOEXCEPT
#endif

class StrVec
{
public:
    StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) { }
    StrVec(std::initializer_list<std::string>);
    StrVec(const StrVec&);
    StrVec& operator=(const StrVec&);
    StrVec(StrVec&&) NOEXCEPT;
    StrVec& operator=(StrVec&&)NOEXCEPT;
    ~StrVec();

    void push_back(const std::string&);
    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }
    std::string *begin() const { return elements; }
    std::string *end() const { return first_free; }

    std::string& at(size_t pos) { return *(elements + pos); }
    const std::string& at(size_t pos) const { return *(elements + pos); }

    void reserve(size_t new_cap);
    void resize(size_t count);
    void resize(size_t count, const std::string&);

private:
    std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);
    void free();
    void chk_n_alloc() { if (size() == capacity()) reallocate(); }
    void reallocate();
    void alloc_n_move(size_t new_cap);
    void range_initialize(const std::string*, const std::string*);

private:
    std::string *elements;
    std::string *first_free;
    std::string *cap;
    static std::allocator<std::string> alloc;
};

#endif

Implementation

#include <algorithm> // for_each

void StrVec::push_back(const std::string &s)
{
    chk_n_alloc();
    alloc.construct(first_free++, s);
}

std::pair<std::string*, std::string*>
StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{
    auto data = alloc.allocate(e - b);
    return{ data, std::uninitialized_copy(b, e, data) };
}

void StrVec::free()
{
    if (elements) {
        for_each(elements, first_free, [this](std::string &rhs){ alloc.destroy(&rhs); });
        alloc.deallocate(elements, cap - elements);
    }
}

void StrVec::range_initialize(const std::string *first, const std::string *last)
{
    auto newdata = alloc_n_copy(first, last);
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec::StrVec(const StrVec &rhs)
{
    range_initialize(rhs.begin(), rhs.end());
}

StrVec::StrVec(std::initializer_list<std::string> il)
{
    range_initialize(il.begin(), il.end());
}

StrVec::~StrVec()
{
    free();
}

StrVec& StrVec::operator = (const StrVec &rhs)
{
    auto data = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

void StrVec::alloc_n_move(size_t new_cap)
{
    auto newdata = alloc.allocate(new_cap);
    auto dest = newdata;
    auto elem = elements;
    for (size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, std::move(*elem++));
    free();
    elements = newdata;
    first_free = dest;
    cap = elements + new_cap;
}

void StrVec::reallocate()
{
    auto newcapacity = size() ? 2 * size() : 1;
    alloc_n_move(newcapacity);
}

void StrVec::reserve(size_t new_cap)
{
    if (new_cap <= capacity()) return;
    alloc_n_move(new_cap);
}

void StrVec::resize(size_t count)
{
    resize(count, std::string());
}

void StrVec::resize(size_t count, const std::string &s)
{
    if (count > size()) {
        if (count > capacity()) reserve(count * 2);
        for (size_t i = size(); i != count; ++i)
            alloc.construct(first_free++, s);
    }
    else if (count < size()) {
        while (first_free != elements + count)
            alloc.destroy(--first_free);
    }
}

StrVec::StrVec(StrVec &&s) NOEXCEPT : elements(s.elements), first_free(s.first_free), cap(s.cap)
{
    // leave s in a state in which it is safe to run the destructor.
    s.elements = s.first_free = s.cap = nullptr;
}

StrVec& StrVec::operator = (StrVec &&rhs) NOEXCEPT
{
    if (this != &rhs) {
        free();
        elements = rhs.elements;
        first_free = rhs.first_free;
        cap = rhs.cap;
        rhs.elements = rhs.first_free = rhs.cap = nullptr;
    }
    return *this;
}



Move Constructors and std::move,However, we do know that move constructors typically operate by “moving” resources from the given object to the object being constructed.We also know that the library guarantees that the“moved-from” string remains in a valid, destructible state

Praticing String that is a simplified version of the library string class

     Write a class named String that is a simplified version of the library string class. Your class should have at least a default constructor and a constructor that takes a pointer to a C-style string. Use an allocator to allocate memory that your String class uses.


String.h

#ifndef STRING_H_INCLUDED
#define STRING_H_INCLUDED
#include<memory>
#include<utility>

class String
{
public:
    String():it_start(nullptr),it_end(nullptr){}
    String(const char *);
    String(const String &);
    String& operator=(const String &rhs);// look later
    //String& operator=(String rhs);// look later
    ~String();

    const char *c_str() const { return it_start; }
    size_t size() const {return it_end-it_start;}
    size_t length()const {return it_end - it_start-1;}
    char* begin()  const {return it_start;}
    char* end()    const {return it_end;}
    void swap(String &);
private:
    std::allocator<char> alloc;
    std::pair<char*,char*> copy_n_alloc(const char *s,const char *e);
    void rang_initializer(const char* s,const char *e);
    void free();
private:
    char *it_start;
    char *it_end;
};

#endif // STRING_H_INCLUDED
String.cpp

#include"String.h"
#include<algorithm>
#include<string.h>
#include<iostream>
//allocate function

std::pair<char*,char*>
String::copy_n_alloc(const char* s,const char *e)
{
    //e>s
   char *allocChar = alloc.allocate(e-s);
   return {allocChar,std::uninitialized_copy(s,e,allocChar)};
}

void String::rang_initializer(const char *s,const char *e)
{
    auto it = copy_n_alloc(s,e);
    it_start = it.first;
    it_end = it.second;
}
void String::free()
{
    // cause the allocate must destroy the memory allocated by allocator
    if(it_start)
    {
        auto  it = it_start;
        while(it!=it_end)
        {
            alloc.destroy(it++);
        }
        alloc.deallocate(it_start,it_end-it_start);
    }
}

//copy control
String::String(const char * s) //the parameter is the pointer to which is the C-style String  null-terminate end;
{
    auto it_end_s = const_cast<char *>(s);
    // the last char of s must be null-terminate
    while(*it_end_s) it_end_s++;

    // warning here
    rang_initializer(s,++it_end_s);
    std::cout<<"initializer "<<std::endl;
}

String::String(const String &rhs)
{
    rang_initializer(rhs.begin(),rhs.end());
    std::cout<<"copy constructor"<<std::endl;
}

String::~String()
{
    free();
}

String& String::operator=(const String &rhs)
{
    auto it = copy_n_alloc(rhs.begin(),rhs.end());
    free();
    it_start = it.first;
    it_end = it.second;
    std::cout<<"copy operator="<<std::endl;
    return *this;
}


test
#include "String.h"
#include <vector>
#include <iostream>

void foo(String x)    //copy constructor
{
    std::cout << x.c_str() << std::endl;
}
void bar(const String& x)
{
    std::cout << x.c_str() << std::endl;
}

String baz()
{
    String ret("world");
    return ret;
}

int main()
{
    char text[] = "world";
    String s0;           // default constructor
    String s1("hello");  //initializer
    String s2(s0);       //copy constructor
    String s3 = s1;      //copy constructor
    String s4(text);     //initializer
    s2 = s1;             //copy operator=

    foo(s1);
    bar(s1);
    foo("temporary");      //initializer
    bar("temporary");      //initializer
    String s5 = baz();     //copy constructor

    std::vector<String> svec;
    svec.reserve(8);
    svec.push_back(s0);   //all copy constructor
    svec.push_back(s1);
    svec.push_back(s2);
    svec.push_back(s3);
    svec.push_back(s4);
    svec.push_back(s5);
    svec.push_back(baz());
    svec.push_back("good job");

    for (auto s : svec) {
        std::cout << s.c_str() << std::endl;
    }
}

note:

//copy control
String::String(const char * s) //the parameter is the pointer to which is the C-style String  null-terminate end;
{
    auto it_end_s = const_cast<char *>(s);
    // the last char of s must be null-terminate
    while(*it_end_s) it_end_s++;

    // warning here
    rang_initializer(s,++it_end_s);
    std::cout<<"initializer "<<std::endl;
}


Add a move constructor and move-assignment operator For String Class

String.h

#include <memory>

#ifndef _MSC_VER
#define NOEXCEPT noexcept
#else
#define NOEXCEPT
#endif

class String
{
public:
    String() : String("") { }
    String(const char *);
    String(const String&);
    String& operator=(const String&);
    String(String &&) NOEXCEPT;
    String& operator=(String&&)NOEXCEPT;
    ~String();

    const char *c_str() const { return elements; }
    size_t size() const { return end - elements; }
    size_t length() const { return end - elements - 1; }

private:
    std::pair<char*, char*> alloc_n_copy(const char*, const char*);
    void range_initializer(const char*, const char*);
    void free();

private:
    char *elements;
    char *end;
    std::allocator<char> alloc;
};

#endif
String.cpp
#include <algorithm>
#include"String.h"
std::pair<char*, char*>
String::alloc_n_copy(const char *b, const char *e)
{
    auto str = alloc.allocate(e - b);
    return{ str, std::uninitialized_copy(b, e, str) };
}

void String::range_initializer(const char *first, const char *last)
{
    auto newstr = alloc_n_copy(first, last);
    elements = newstr.first;
    end = newstr.second;
}

String::String(const char *s)
{
    char *sl = const_cast<char*>(s);
    while (*sl)
        ++sl;
    range_initializer(s, ++sl);
}

String::String(const String& rhs)
{
    range_initializer(rhs.elements, rhs.end);
}

void String::free()
{
    if (elements) {
        std::for_each(elements, end, [this](char &c){ alloc.destroy(&c); });
        alloc.deallocate(elements, end - elements);
    }
}

String::~String()
{
    free();
}

String& String::operator = (const String &rhs)
{
    auto newstr = alloc_n_copy(rhs.elements, rhs.end);
    free();
    elements = newstr.first;
    end = newstr.second;
    return *this;
}

String::String(String &&s) NOEXCEPT : elements(s.elements), end(s.end)
{
    s.elements = s.end = nullptr;
}

String& String::operator = (String &&rhs) NOEXCEPT
{
    if (this != &rhs) {
        free();
        elements = rhs.elements;
        end = rhs.end;
        rhs.elements = rhs.end = nullptr;
    }
    return *this;
}

Test:

#include <iostream>
#include"String.h"
#include"StrVec.h"
#include<vector>
#include<string>
using namespace std;
void foo(String x)    //copy constructor
{
    std::cout << x.c_str() << std::endl;
}
void bar(const String& x)
{
    std::cout << x.c_str() << std::endl;
}

String baz()
{
    String ret("world");
    return ret;
}

int main()
{
    char text[] = "world";
    String s0;           // default constructor
    String s1("hello");  //initializer
    String s2(s0);       //copy constructor
    String s3 = s1;      //copy constructor
    String s4(text);     //initializer
    s2 = s1;             //copy operator=

    foo(s1);
    bar(s1);
    foo("temporary");      //initializer
    bar("temporary");      //initializer
    String s5 = baz();
    std::cout<<"==============\n";
    std::vector<String> svec;
    svec.reserve(8);
    svec.push_back(s0);   //all copy constructor
    svec.push_back(s1);
    svec.push_back(s2);
    svec.push_back(s3);
    svec.push_back(s4);
    svec.push_back(s5);
    svec.push_back(baz());     //move constructor
    svec.push_back("good job");//move constructor

    for (auto s : svec) {
        std::cout << s.c_str() << std::endl;
    }
}

/*
String baz()
{
    String ret("world");
    return ret; // first avoided
}

String s5 = baz(); // second avoided
*/


Reference(Exercise 13.49): https://github.com/Mooophy/Cpp-Primer/blob/master/ch13/README.md



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的vector是一个动态数组容器,可以存储不同类型的元素。而stringC++中的字符串类,用于处理字符串操作。 使用vector时,需要包含头文件< vector >。下面是vector的一些常见用法: 1. 创建vector对象: ``` vector<int> vec; // 创建一个空的整型向量 vector<string> strVec(5); // 创建一个包含5个空字符串的字符串向量 vector<double> doubleVec(10, 3.14); // 创建一个包含10个初始值为3.14的双精度浮点数向量 ``` 2. 向vector中添加元素: ``` vec.push_back(10); // 在向量末尾添加一个元素 vec.insert(vec.begin(), 5); // 在向量开头插入一个元素 ``` 3. 访问vector中的元素: ``` int num = vec[0]; // 通过下标访问元素 int size = vec.size(); // 获取向量的大小 ``` 4. 遍历vector中的元素: ``` for (int i = 0; i < vec.size(); i++) { cout << vec[i] << " "; } ``` 使用string时,需要包含头文件< string >。下面是string的一些常见用法: 1. 创建string对象: ``` string str; // 创建一个空字符串 string greeting = "Hello"; // 创建一个包含初始值的字符串 ``` 2. 连接字符串: ``` string fullName = firstName + " " + lastName; // 使用+运算符连接字符串 fullName.append(" Jr."); // 在字符串末尾添加另一个字符串 ``` 3. 获取字符串长度: ``` int length = str.length(); // 获取字符串的长度 ``` 4. 截取子串: ``` string subStr = str.substr(startIndex, length); // 从指定位置开始截取指定长度的子串 ``` 5. 查找子串: ``` size_t found = str.find("world"); // 查找子串在字符串中的位置 if (found != string::npos) { cout << "子串找到了!" << endl; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值