2

I am creating a string class and I have set up my default constructor. I want to know how to go about setting up the copy constructor.

I have already tried the default constructor and the c string constructor. I am confused on what I should do next.

DSString::DSString() { data = nullptr; } DSString::DSString(const char* d) { data = new char[strlen(d) + 1]; strcpy(data,d); } DSString::DSString(const DSString&) { } 

I expect the result to be functional. but it is not functional right now.

9
  • 2
    Why aren't you using std::string instead? Commented Aug 30, 2019 at 18:33
  • 1
    You just need to copy the member variable data Commented Aug 30, 2019 at 18:33
  • 2
    Just don't. Use string, vector... and avoid direct usage of new/delete. No explicit definitions of copy/destruction operations will be necessary. Commented Aug 30, 2019 at 18:33
  • 3
    @JosephLarson std::string is less advanced (simpler and safer to use) than new and delete. Commented Aug 30, 2019 at 18:42
  • 2
    @FrançoisAndrieux You can pick whatever semantics you want. Understanding how string might work under the hood is a useful exercise. He's learning the language. This is an excellent example for learning about memory management. Commented Aug 30, 2019 at 18:48

2 Answers 2

7

You can do the same kind of initialization in your DSString& copy constructor that you do in your char* converting constructor, simply use the data member of the input DSString as the char* to copy from, eg:

DSString::DSString(const char *d) { // the behavior of strlen() and strcpy() are undefined if d is null... if (d) { data = new char[strlen(d) + 1]; strcpy(data, d); } else data = nullptr; } DSString::DSString(const DSString &src) { // the behavior of strlen() and strcpy() are undefined if src.data is null... if (src.data) { data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } else data = nullptr; } 

Or, since you are clearly using C++11 or later (by virtue of the use of nullptr), you can simply have your copy constructor delegate to your converting constructor itself to avoid repeating code:

DSString::DSString(const DSString &src) : DSString(src.data) { } 

Don't forget to also include a destructor, a move constructor, a copy assignment operator, and a move assignment operator, per the Rule of 3/5/0:

DSString::DSString(DSString &&src) : DSString() { //std::swap(data, src.data); data = src.data; src.data = nullptr; } DSString::~DSString() { delete[] data; } DSString& DSString::operator=(const DSString &rhs) { if (&rhs != this) { DSString tmp(rhs); //std::swap(data, tmp.data); char *d = data; data = tmp.data; tmp.data = d; } return *this; } DSString& DSString::operator=(DSString &&rhs) { DSString tmp(std::move(rhs)); //std::swap(data, tmp.data); char *d = data; data = tmp.data; tmp.data = d; return *this; } 

Now, that being said, if you were to use std::string instead of char* for your data member (as you should), you get most of this functionality for free from the compiler, you don't have to implement it manually, except for the char* converting constructor:

class DSString { private: std::string data; public: DSString() = default; DSString(const char *d) : data(d ? d : "") {} DSString(const DSString&) = default; DSString(DSString&&) = default; ~DSString() = default; DSString& operator=(const DSString&) = default; DSString& operator=(DSString&&) = default; }; 
Sign up to request clarification or add additional context in comments.

6 Comments

@RemyLebeau I'm curious, why did you add self-assignment protection to the copy assignment, but not to the move assignment? Both should work fine without one.
@HolyBlackCat Actually, the move assignment operator DOES have self-assignment protection, it is just implicit via the tmp variable, and the fact that self-assignment is almost never performed via a move assignment to begin with
"DOES have [implicit] self-assignment protection" I think same applies to the copy assignment. (even if it didn't have the if (&rhs != this), that is)
@HolyBlackCat technically yes, having the copy assignment operator use tmp unconditionally would also provide self-assignment protection, but removing the self-assignment check forces a copy to always be made unconditionally, which may be costly. The explicit check avoids a self copy. The same issue does not exist in move assignment. Anyway, under C++11 and later, a better choice is usually to merge copy assignment and move assignment into a single operator that takes input by value and let the constructor decide whether to copy/move the data, the operator unconditionally moves the result.
@HolyBlackCat DSString& DSString::operator=(DSString rhs) { DSString tmp(std::move(rhs)); std::swap(data, tmp.data); return *this; } Efficiently handles both copy assignment and move assignment and self-assignment protection.
|
1

I started with Remy's version. My version is more NULL-safe and shows how to use initializer lists in your constructors.

DSString::DSString() : data(nullptr) { } DSString::DSString(const char* d) : data(nullptr) { if (d != nullptr) { data = new char[strlen(d) + 1]; strcpy(data,d); } } DSString::DSString(const DSString &src) : data(nullptr) { if (src.data != nullptr) { data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } } DSString::~DSString() { if (data != nullptr) { delete[] data; data = nullptr; } } DSString & DSString::operator=(const DSString &rhs) { // This if protects against self-assignment. if (&rhs != this) { DSString tmp(rhs); std::swap(data, tmp.data); } return *this; } 

3 Comments

If you are going to teach the OP about language features like initialization lists, then you could take it a step further by setting data to nullptr right in the class declaration itself, removing the explicit default constructor and default it, and have the other constructors delegate to the compiler-generated default constructor. And there is no need to check data for nullptr when calling delete[].
@RemyLebeau It's also not necessary to do data = nullptr in the destructor, but I do it anyway because I like to keep my objects in a proper state, even during destruction. Is it necessary? No. But it makes me feel better to always null-out my pointers when I free them. As for the first part of what you've said -- show him how. I've never done that.
"I've never done that" - Like this: class DSString { private: char *data = nullptr; public: DSString() = default; ...}; ... DSString::DSString(const char* d) : DSString() { ... } ... DSString(const DSString &src) : DSString() { ... }

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.