Creating an easy to maintain copy constructor

冷暖自知 提交于 2019-12-04 00:29:05

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.

utnapistim

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).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!