开发者

Creating an easy to maintain copy constructor

Consider the following class:

class A {

char *p;
int a, b, c, d;

public:
   A(const &A);
};

Note that I have to define a copy constructor in order to do a deep copy of "p". This has two issues:

  1. Most of the fields should simply be copied. Copying them one by one is ugly and error prone.

  2. More importantly, whenever a new attribute is added to the class, the copy constructor needs to be updated, which creates a maintenance nightmare.

I would personally like to do something like:

A(const A &a) : A(a)
{
   // do deep copy of p
   :::
}

So the default copy constructor is called first and then the deep copy is performed.

Unfortunately this doesn't seem to work.

Is there any better way to do this? One restriction - I can't use shared/smart pointers.


Sbi's suggestions make a lot of sense. I think I'll go with creating wrapper classes for handling the resource. I don't want to user shared_ptr since boost libraries may not be available on all platforms (at least not in standard distributions, OpenSolaris is an example).

I still think it would have been great if you could somehow make the compiler to create the default constructor/assignment operators for you and you could just add your functionality on top of it. The manually created copy constructor/assignment operator functions I think will be a hassle to create and a nightmare to maintain. So my personal rule of thumb would be to avoid custom copy constructors/assignment operators at all cost.

Thanks everybody for their responses and helpful information and sorry about typos in my quest开发者_如何学Cion. I was typing it from my phone.


As a rule of thumb: If you have to manually manage resources, wrap each into its own object.

Put that char* into its own object with a proper copy constructor and let the compiler do the copy constructor for A. Note that this also deals with assignment and destruction, which you haven't mentioned in your question, but need to be dealt with nevertheless.
The standard library has several types to pick from for that, among them std::string and std::vector<char>.


Replace char* with std::string.


Always use RAII objects to manage unmanages resources such as raw pointers, and use exactly one RAII object for each resource. Avoid raw pointers in general. In this case, using std::string is the best solution.

If that's not possible for some reason, factor the easy to copy parts out into a base class or a member object.


You could separate your copyable members into a POD-struct and mantain your members requiring a managed copy separately.

As your data members are private this can be invisible to clients of your class.

E.g.

class A {

char *p;

struct POData {
    int a, b, c, d;
    // other copyable members
} data;

public:
   A(const &A);
};

A(const A& a)
    : data( a.data )
{
    p = DuplicateString( a.p );
    // other managed copies...
    // careful exception safe implementation, etc.
}


You really should use smart pointers here.

This would avoid rewriting both the copy constructor and the affectation operator (operator=).

Both of these are error prone.

A common mistake with the operator= is implementing it that way:

SomeClass& operator=(const SomeClass& b)
{
  delete this->pointer;
  this->pointer = new char(*b.pointer); // What if &b == this or if new throws ?

  return *this;
}

Which fails when one does:

SomeClass a;
a = a; // This will crash :)

Smart pointers already handle those cases and are obviously less error prone.

Moreover, Smart pointers, like boost::shared_ptr can even handle a custom deallocation function (by default it uses delete). In practice, I rarely faced a situation where using a smart pointer instead of a raw pointer was unpractical.

Just a quick note: boost smart pointer class, are header-only designed (based on templates) so they don't require additional dependencies. (Sometimes, it matters) You can just include them and everything should be fine.


The question is, do you really need a pointer with deep-copy semantics in your class? In my experience, the answer almost always is no. Maybe you could explain your scenario, so we may show you alternative solutions.

That said, this article describes an implementation of a smart-pointer with deep-copy semantics.


While I agree with others saying that you should wrap the pointer in its own class for RAII and let the compiler synthesise the copy contructor, destructor and assignment operator there is a way around your problem: declare (and define) private static function which will do whatever is needed and common for different constructors and call it from there.


Unless your class has one function, which is managing a resource, you should never manage any resources directly. Always use a smart pointer or custom management class of some description. Typically, it's best to leave the implicit copy constructor, if you can. This approach also allows easy maintenance of the destructor and assignment operators.


So the default copy constructor is called first and then the deep copy is performed. Unfortunately this doesn't seem to work.

Is there any better way to do this? One restriction - I can't use shared/smart pointers.

If I understand correctly, your question, you could consider using an initialization function:

class A
{
    int i, j;
    char* p;

    void Copy(int ii, int jj, char* pp); // assign the values to memebers of A
public:
    A(int i, int j, char* p);
    A(const A& a);
};

A::A(int i, int j, char* p)
{
    Copy(i, j, p);
}

A::A(const A& a)
{
    Copy(a.i, a.j, a.p);
}

That said, you really should consider using RAII ( there's a reason people keep recommending it :) ) for your extra resources.

If I can't use RAII, I still prefer creating the copy constructor and using initializer lists, for every member (actually, I prefer doing so even when using RAII):

A::A(int ii, int lj, char* pp)
    : i(ii)
    , j(jj)
    , p( function_that_creates_deep_copy(pp) )
{
}

A::A(const A& a)
    : i(a.i)
    , j(a.j)
    , p( function_that_creates_deep_copy(a.p) )
{
}

This has the advantage of "explicitness" and is easy to debug (you can step in and see what it does for each initialization).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜