C ++拷贝构造函数和浅拷贝

时间:2010-03-24 14:10:20

标签: c++

假设我有一个具有许多显式(静态分配)成员的类和几个动态分配的指针。

当我在巫婆中声明一个复制构造函数时,我会对手动分配的成员进行深层复制,我不想复制每个静态分配的成员explicite。

如何在显式复制构造函数中使用隐式(默认)复制构造函数?

6 个答案:

答案 0 :(得分:5)

使用包含

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
};

答案 1 :(得分:3)

  

“分配的几个指针   动态”。

危险,威尔罗宾逊!编写一个包含多个原始指针的类并且在其生命周期操作中提供强大的异常保证是非常困难的,当然不值得努力。例如,请考虑以下事项:

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;
    }
    ...
};

现在,假设b的分配被抛出。 MyClass 的析构函数不会被称为,因此分配给a的内存被泄露。因此,您需要明确捕获异常,并且通常会使用错误处理来混淆代码。

相反,定义一个只处理一个指针的小助手类:

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) {}
};

现在,MyClass不需要显式的复制构造函数,析构函数或赋值运算符。默认设置很好。如果b的初始化程序抛出,则仍然不会调用MyClass的析构函数。但是因为已经构造了成员a,所以它的析构函数调用,并释放内存。

所以,除了获得异常安全之外,你还减少了编写一个单独的显式副本,为MyClass的每个成员编写一些代码,编写几个小的显式副本,为每种类型的指针成员编写一些代码的问题。在MyClass中,可以在其他包含相同深度副本的指针的类中重用。

最后,每次你来编写其中一个小助手类时,问问自己是否有一个标准的库类为你做同样的工作。在我的示例中,有两个强有力的候选人:std::stringstd::vector<char>。在copy_ptrclone_ptr周围还有各种可能适用的实施方案。

答案 2 :(得分:1)

你做不到。只需在初始化列表中逐个分配成员。

答案 3 :(得分:1)

如果您的结构包含多个指针,那么您将遇到正确执行问题。除非你实际上是在创建一个智能指针,否则你应该将所有指针都包含在你的对象中(这样你就不会有任何指针)。

//没有智能指针就这样做。很难。
让我演示(我可能会错,因为它很难)

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;
            }
        }
};

在课堂上只使用1个指针这样做更容易,但通常不值得付出努力 你的类包含的指针越多,复制构造函数就越复杂(正确地做)。结果将指针包装在一个适当的包装器中,该包装器完成了为该一个指针获取正确代码的所有工作。记住每个类的MAX 1指针。

在上面的例子中,像动态分配一样的数组包装器是std :: vector,而std :: auto_ptr的单个对象可以工作,我们可以将上面的代码简化为:

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.
};

由于std :: auto_ptr没有正确的语法。我实际上已经为内部使用auto_ptr的单个版本编写了一个包装器。但是这个简单的单个附加类(加上向量)使得A的实现很简单。

答案 4 :(得分:1)

可以通过一些间接完成......我来了:)

它基于boost::shared_ptr的实现,并且可以从良好的加速中受益,如果不是保持指向内存的指针,我们实际上将两个内存块粘合在一起......但是对齐问题等...所以我不会做到这一点。

首先,我们需要一个类,其目的是管理我们的内存,并在必要时提供自定义解除分配器。

它是间接的,这就是神奇的地方。

请注意,它实现了深度复制行为。

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

然后我们可以制作我们的Pimpl课程,因为这就是你的课程。

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

好的,pfiou!

现在让我们使用它:)

// 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()) {}

是的,这很神奇;)

背后的原则是呼叫Type Erasure。该机制确保一旦构建MemoryOwner对象,它就知道如何删除它所拥有的内存,并通过间接隐藏来自调用者的确切机制。

因此,您可以将Pimpl<T>对象视为值:

  • DeepCopy语义
  • DeepConst语义(volatile被忽略...)
  • 默认的CopyConstructor,Assignment Operator和Destructor都没问题

但要注意它隐藏了一个指针,在解除引用它之前确保它是非空的是你的角色。

如果删除mOwner参数的延迟初始化,则可以大大简化代码。此外,一些例外安全约束:处置者复制构造函数应该是无抛出,否则所有投注都是关闭的。

修改

说明

这里的问题是代码绝缘。无论指向何种类型,都可以对指针执行许多操作,但是对于创建或销毁,我们需要知道基础类型。

4基础方法中需要创建和销毁,以及基础类型的知识:

  • 构造
  • 复制构造函数
  • 分配操作员(破坏旧值)
  • 析构

实现价值语义本身就是必需的。

C++中,有一个名为type erasure的习语,它包含在虚拟接口后面嵌入类型信息。因此,设计的第一部分:

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

MemoryOwnerBase提供我们正在寻找的基本操作(构造,深度复制和销毁),并隐藏类型特定信息(如何正确删除)。

MemoryOwner实现virtual的{​​{1}}方法,并通过其MemoryOwnerBase(用于处理程序)参数来封装销毁指针所需的知识。

现在,为了操作D,我们需要一个指向它的指针/引用,它没有值语义,因此我们将它包装在MemoryOwnerBase类(代表)中指向实现的指针)具有适当的值语义。

请注意,只有处理程序(用于销毁)才需要换行,因为用户需要自己提供指针,从而使用Pimpl运算符。

一个改进是提供一个new方法,可以看到内存分配等......但由于前面提到的存储对齐问题,我还没有完成它。

答案 5 :(得分:0)

我会在结构下声明动态分配的成员,然后在memcpy()中声明它们;

#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);
}

你必须在结构中“分组”成员,所以当使用memcpy()时,你不会覆盖某些C ++“其他数据”,例如虚函数指针。