开发者

C++ copy constructor and shallow copy

开发者 https://www.devze.com 2022-12-24 17:25 出处:网络
suppose I have a class with many explicit (statically allocated) members and few pointers that are allocated dynamically.

suppose I have a class with many explicit (statically allocated) members and few pointers that are allocated dynamically.

When I declare a copy constructor in witch I make a deep copy of manually allocated members, I wouldn't like t开发者_如何学运维o copy each statically allocated member explicite.

How can I use implicit (default) copy constructor functionality in explicit copy constructor?


Use containment:

class outer
{
public:

    outer( const outer& other ) :
        members_( other_.members_ ),
        pmember_( deep_copy( other.pmember_ ))
    {}

    // DON'T FORGET ABOUT THESE TOO

    outer& operator=( const outer& );
    ~outer();

private:

    struct inner
    {
        inner( int i, float f ) :
            int_( i ), float_( f )
        {}

        int   int_;
        float float_;
    };

    inner      members_; //< direct members
    something* pmember_; //< indirect member
};


"a few pointers that are allocated dynamically".

Danger, Will Robinson! It's very difficult, certainly not worth the effort, to write a class which holds several raw pointers and which offers the strong exception guarantee on its lifecycle operations. For example, consider the following:

struct MyClass {
    char *a;
    char *b;
    int i;
    float f;
    MyClass(int i, float f): a(0), b(0), i(i), f(f) {
        a = new char[12];
        b = new char[23];
    }
    ~MyClass() {
        delete[] a;
        delete[] b;
    }
    ...
};

Now, suppose that the allocation of b throws. The destructor of MyClass is not called, so the memory allocated for a is leaked. So you'd need to explicitly catch the exception and generally clutter your code with error-handling.

Instead, define a little helper class that deals with just one pointer:

struct MyPointerHolder {
    char *value;
    MyPointerHolder(int s) : value(new char[s]) {}
    ~MyPointerHolder() { delete[] value; }
    MyPointerHolder(const MyPointerHolder &rhs) {
        // perform deep copy
    }
    MyPointerHolder &operator=(const MyPointerHolder &rhs) {
        // sample implementation - you might be able to do better
        // by for example just copying the bytes from rhs.
        MyPointerHolder tmp(rhs);
        std::swap(value, tmp.value);
    }
};

struct MyClass {
    MyPointerHolder a;
    MyPointerHolder b;
    int i;
    float f;
    MyClass(int i, float f) : a(12), b(23), i(i) f(f) {}
};

Now MyClass doesn't need an explicit copy constructor, destructor, or assignment operator. The defaults are fine. If the initializer for b throws, the destructor for MyClass still isn't called. But because the member a has been constructed, its destructor is called, and frees the memory.

So, quite aside from gaining exception safety, you've also reduced your problem of writing one honking great explicit copy, writing some code per member of MyClass, into writing several small explicit copies, writing some code per type of pointer member in MyClass, which can be reused in other classes that hold pointers with the same deep copy.

Finally, each time you come to write one of those little helper classes, ask yourself whether there's a standard library class which does the same job for you. In my example, there are two strong candidates: std::string and std::vector<char>. There are also various proposed implementations floating around of copy_ptr or clone_ptr that might suit.


You can't. Just assign the members one by one in the initialization list.


It can be done, with some indirection... Here I come :)

It is based on the implementation of boost::shared_ptr and could benefit from a good speed-up if instead of holding the a pointer to the memory we were actually gluing together the two memory blocks... but then there are alignment issues etc... so I won't do it off the top of my hat.

First, we need a class whose purpose is to manage our memory, with the possibility of supplying a custom deallocator if necessary.

It's indirected, and that's where the magic is.

Notice that it implements a deep-copying behavior.

namespace detail
{
  // The interface
  template <class T>
  class MemoryOwnerBase
  {
  public:
    virtual ~MemoryOwnerBase() { this->dispose(mItem); mItem = 0; }

    virtual void dispose(T* item) = 0;
    virtual void clone() const = 0;

    T* get() { return mItem; }
    T* release() { T* tmp = mItem; mItem = 0; return tmp; }

    void reset(T* item = 0)
    { 
      if (mItem && item != mItem) this->dispose(mItem);
      mItem = item;
    }

  protected:
    explicit MemoryOwnerBase(T* i = 0): mItem(i) {}
    MemoryOwnerBase(const MemoryOwnerBase& rhs): mItem(0)
    {
      if (rhs.mItem) mItem = new_clone(*rhs.mItem); // Boost Clonable concept
    }
    MemoryOwnerBase& operator=(const MemoryOwnerBase& rhs)
    {
      MemoryOwnerBase tmp(rhs);
      this->swap(rhs);
      return *this;
    }

  private:
    T* mItem;
  };

  // by default, call delete
  template <class T>
  struct DefaultDisposer
  {
    void dispose(T* item) { delete item; }
  };

  // the real class, the type of the disposer is erased from the point of view
  // of its creator
  template <class T, class D = DefaultDisposer<T> >
  class MemoryOwner: public MemoryOwnerBase, private D // EBO
  {
  public:
    MemoryOwner(): MemoryOwnerBase(0), D() {}
    explicit MemoryOwner(T* item): MemoryOwnerBase(item), D() {}
    MemoryOwner(T* item, D disposer): MemoryOwnerBase(item), D(disposer) {}

    virtual void dispose(T* item) { ((D&)*this).dispose(item); }
    virtual MemoryOwner* clone() const { return new MemoryOwner(*this); }
  };

  // easier with type detection
  template <class T>
  MemoryOwnerBase<T>* make_owner(T* item)
  {
    return new MemoryOwner<T>(item);
  }

  template <class T, class D>
  MemoryOwnerBase<T>* make_owner(T* item, D d)
  {
    return new MemoryOwner<T,D>(item,d);
  }
} // namespace detail

Then we can craft our Pimpl class, since it's what your after.

template <class T>
class Pimpl
{
  typedef detail::MemoryOwnerBase<T> owner_base;
public:
  Pimpl(): mItem(0), mOwner(0) {}

  explicit Pimpl(T* item):
    mItem(item), mOwner(item == 0 ? 0 : detail::make_owner(item)) {}

  template <class D>
  Pimpl(T* item, D d):
    mItem(item), mOwner(item == 0 ? 0 : detail::make_owner(item, d)) {}

  Pimpl(const Pimpl& rhs): mItem(), mOwner()
  {
    if (rhs.mOwner)
    {
      mOwner = rhs.mOwner.clone();
      mItem = mOwner->get();
    }
  } 

  T* get() { return mItem; }
  const T* get() const { return mItem; }

  void reset(T* item = 0)
  {
    if (item && !mOwner)
      mOwner = detail::make_owner(item);

    if (mOwner)
    {
      mOwner->reset(item);
      mItem = mOwner->get();
    }
  }

  template <class D>
  void reset(T* item, D d)
  {
    if (mOwner)
    {
      if (mItem == item) mOwner->release();
      delete mOwner;
    }
    mOwner = detail::make_owner(item, d);
    mItem = item;
  }

  T* operator->() { return mItem; }
  const T* operator->() const { return mItem; }

  T& operator*() { return *mItem; }
  const T& operator*() const { return *mItem; }

private:
  T* mItem;                            // Proxy for faster memory access
  detail::MemoryOwnerBase<T>* mOwner;  // Memory owner
}; // class Pimpl

Okay, pfiou!

Let's use it now :)

// myClass.h
class MyClass
{
public:
  MyClass();

private:
  struct Impl;
  Pimpl<Impl> mImpl;
};

// myClass.cpp
struct MyClass::Impl
{
  Impl(): mA(0), mB(0) {}
  int mA;
  int mB;
};

// Choice 1
// Easy
MyClass::MyClass(): mImpl(new Impl()) {}

// Choice 2
// Special purpose allocator (pool ?)
struct MyAllocatorDeleter
{
  void dispose(Impl* item) { /* my own routine */ }
};

MyClass::MyClass(): mImpl(new Impl(), MyAllocatorDeleter()) {}

Yes it's magical ;)

The principle behind is call Type Erasure. The mechanism ensures that once the MemoryOwner object is built, it knows how to delete the memory it holds and hide the exact mechanism from the caller through an indirection.

You can thus treat the Pimpl<T> object as a value:

  • DeepCopy semantics
  • DeepConst semantics (volatile is ignored...)
  • Default CopyConstructor, Assignment Operator and Destructor are fine

But beware it hides a pointer and it is your role to make sure it's non-null before dereferencing it.

The code can be greatly simplified if you remove the lazy initialization of the mOwner parameter. Also, some exceptions safety constraints: the disposer copy constructor should be a no-throw, otherwise all bets are off.

EDIT:

Explanations.

The problem here is code insulation. A number of operations can be performed on a pointer regardless of the type pointed to, but for the creation or destruction, we need to know the underlying type.

Creation and Destruction, and thus knowledge about the underlying type, are required in the 4 fundamentals methods:

  • Constructor
  • Copy Constructor
  • Assignment Operator (destruction of the old value)
  • Destructor

Which are themselves required to achieve value semantics.

In C++, there is an idiom called type erasure which consists into embedding type information behind a virtual interface. And thus the first part of the design:

template <class T> class MemoryOwnerBase {};
template <class T, class D> class MemoryOwner: public MemoryOwnerBase<T> {};

MemoryOwnerBase provide the basic operations (construction, deep-copying and destruction) we are looking for, and hide type specific information (how to properly delete).

MemoryOwner implements the virtual methods of MemoryOwnerBase and encapsulates the knowledge required for destroying the pointers thanks to its D (for disposer) parameter.

Now, in order to manipulate MemoryOwnerBase we need a pointer / reference to it, which does not have value semantics, and thus we wrap it within the Pimpl class (which stands for pointer-to-implementation) which has proper value semantics.

Note that only the a disposer (for destruction) needs to wrap, since the user is expected to provide a pointer by himself and thus to use the new operator.

A refinement would be to provide a Pimpl<T> make_pimpl<T,D>(const T&, const D&) method that would see to the memory allocation etc... but I haven't yet got to it because of the aforementioned storage alignment issues.


If your structure contains more than one pointer you are going to run into problems doing it correctly. Unless you are actually creating a smart pointer you should wrap all your pointers inside within your object (so that you don't actually have any pointers).

// Doing it without smart pointers. Is hard.
Let me demo (and I will probably get this wrong because it is hard)

class D { /* Has a constructor and Destructor */ };
class A
{
    int     a;
    D*      b;
    D*      c;

    public:
        // Default constructor and destructor are easy
        A()
          :a(1)
          ,b(new D)
          ,c(new D[10])
        {}
        ~A()
        {
           delete b;
           delete [] c;
        }
        // Standard copy and swap for assignment.
        // Which pushes all the real work into one place
        A& operator=(A const& copy)
        {
            A  tmp(copy);
            tmp.swap(*this);
            return *this;
        }
        void swap(A& s) throws()
        {
            std::swap(a,s.a);
            std::swap(b,s.b);
            std::swap(c,s.c);
        }
        // OK the hard part getting the copy constructor correct

        A(A const& copy)
        {
            a = copy.a;
            b = new D(copy.b);  // may throw but at this point we don' care
                                // if new throws memory is de-allocated.
                                // If a D constructor throws then all fully constructed members are
                                // destroyed before the memory is de-allocated

            try
            {
                c = new D[10];  // If this throws we do care.
                                // As B needs to be deleted to prevent memory leak
                                // So this whole part needs to be put in a try catch block
                try
                {
                    // This part needs to be in its own try catch block
                    // If the copy constructor throws then  we need to delete c
                    // Note this needs to be seporate to the allocation
                    // As a throw from the call to new that throws will not set c thus calling
                    // delete on it will generate undefined behavior.

                    for(int loop=0;loop < 10;++loop)
                    {
                         std::copy(&copy.c[0],&copy.c[10],c);
                    }
                }
                catch(...)
                {
                    delete [] c;
                    throw;
                }
            }
            catch(...)
            {
                delete b;
                throw;
            }
        }
};

Doing it with only 1 pointer in a class is easier but still usually not worth the effort.
The more pointers your class contains the more convoluted the copy constructor will get (to do correctly). As a result wrap your pointer in an appropriate wrapper that does all the work of getting the code correct for that one pointer. Remember MAX 1 pointer per class.

In the above case the wrapper for array like dynamic allocation is the std::vector, while a single object a std::auto_ptr would have worked and we can simplify the above code as:

class D { /* Has a constructor and Destructor */ };
template<typename T>
class DeepCpyAPtr  // I am surprised I did not find anything like this in boost
{                  // mybe I am over thinking this and will regret it
    std::auto_ptr<T>   data;
    public:
        explicit DeepCpyAPtr(T* d = NULL)    :data(d)                     {}
        // Other constructors as required
        DeepCpyAPtr(DeepCpyAPtr const& copy) :data(new D(copy.data.get())){}
        DeepCpyAPtr& operator=(DeepCpyAPtr const& copy)
        {
            DeepCpyAPtr  t(copy);
            t.data.swap(data);
            return *this;
        }
        // Destructor Magical

        // Add all the methods you need from std::auto_ptr here.
        T*   get()                {return data.get();}
        T&   operator*()          {return data.operator*();}
        T*   operator->()         {return data.operator->();}
        void reset(T* d = NULL)   {data.reset(d);}
};
class A
{
    int                     a;
    DeepCpyAPtr<D>          b;
    std::vector<D>          c;

    public:
        // Default constructor is easy
        A()
          :a(1)
          ,b(new D)
          ,c(10)
        {}
     // All the compiler generated versions work perfectly now.
};

As std::auto_ptr does not quite have the correct symantics. I have actually written a wrapper for the single version that uses auto_ptr internally. But this simple single addtional class (plus vector) makes the implementation of A trivial.


I would declare the dynamically allocated members under a structure and then memcpy() them;

#define _WIN32_WINNT 0x0400
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

class MyClass
{
private:
    static int some_static_member;

public:
    struct DynamicallyAllocated
    {
        int x, y, z;
    } *dyn;

public:
    MyClass(MyClass* copy = NULL)
    {
        this->dyn = NULL;

        if(copy != NULL)
        {
            if(copy->dyn != NULL)
            {
                this->dyn = new DynamicallyAllocated();
                memcpy(this->dyn, copy->dyn, sizeof(*this->dyn));
            }
        }
    }
};

int MyClass::some_static_member = 0;

void main()
{
    MyClass mc1(NULL);
    mc1.dyn = new MyClass::DynamicallyAllocated();
    mc1.dyn->x = 1;
    mc1.dyn->y = 2;
    mc1.dyn->z = 3;
    MyClass mc2(&mc1);
}

You have to "group" the members inside a struct so when using memcpy() you won't overwrite some C++ "other data" such as virtual functions' pointers.

0

精彩评论

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