【问题标题】:Creating an easy to maintain copy constructor创建一个易于维护的复制构造函数
【发布时间】:2011-03-12 16:26:54
【问题描述】:

考虑以下类:

class A {

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

public:
   A(const &A);
};

请注意,我必须定义一个复制构造函数才能对“p”进行深层复制。这有两个问题:

  1. 应该简单地复制大部分字段。一个一个地复制它们既丑陋又容易出错。

  2. 更重要的是,每当向类添加新属性时,都需要更新复制构造函数,这会带来维护的噩梦。

我个人想做这样的事情:

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

所以先调用默认的拷贝构造函数,然后再进行深拷贝。
不幸的是,这似乎不起作用。

有没有更好的方法来做到这一点? 一个限制 - 我不能使用共享/智能指针。


Sbi 的建议很有道理。我想我会创建包装类来处理资源。我不想使用 shared_ptr,因为 boost 库可能并非在所有平台上都可用(至少在标准发行版中不可用,OpenSolaris 就是一个例子)。

我仍然认为,如果你能以某种方式让编译器为你创建默认的构造函数/赋值运算符,并且你可以在上面添加你的功能,那会很棒。我认为手动创建的复制构造函数/赋值运算符函数创建起来很麻烦,维护起来也是一场噩梦。所以我个人的经验法则是不惜一切代价避免自定义复制构造函数/赋值运算符。

感谢大家的回复和有用的信息,并对我的问题中的错别字表示歉意。我是用手机输入的。

【问题讨论】:

  • 为什么不能使用智能指针?
  • @Neil:也许他的键盘没有下划线?
  • 到目前为止,所有答案都集中在不需要自定义复制 c'tor 上。但是这里提出的问题的有趣部分是:是否可以保留一些自动生成的功能并只是扩展它(不仅限于处理指针)。
  • @Gene Vincent:是的,通过向具有自动生成的复制构造函数的类添加派生或组合关系。
  • 如果你需要一个允许深度复制的智能指针,你可以检查 axter smart pointer。对我来说效果很好。否则我也会不时使用查尔斯在答案中使用的技巧。

标签: c++ constructor deep-copy


【解决方案1】:

所以先调用默认的拷贝构造函数,然后进行深拷贝。 不幸的是,这似乎不起作用。

有没有更好的方法来做到这一点?一个限制 - 我不能使用共享/智能指针。

如果我理解正确,您的问题,您可以考虑使用初始化函数:

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

也就是说,您确实应该考虑使用 RAII(人们不断推荐它是有原因的 :))作为您的额外资源。

如果我不能使用 RAII,我仍然更喜欢为每个成员创建复制构造函数并使用初始化列表(实际上,即使使用 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) )
{
}

这具有“显性”的优点,并且易于调试(您可以介入并查看它对每次初始化的作用)。

【讨论】:

    【解决方案2】:

    除非您的课程有一个功能,即管理资源,否则您永远不应该直接管理任何资源。始终使用某种描述的智能指针或自定义管理类。通常,如果可以的话,最好保留隐式复制构造函数。这种方法还可以轻松维护析构函数和赋值运算符。

    【讨论】:

      【解决方案3】:

      你真的应该在这里使用智能指针。

      这将避免重写复制构造函数和做作运算符 (operator=)。

      这两个都容易出错。

      operator= 的一个常见错误就是这样实现的:

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

      如果这样做会失败:

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

      智能指针已经处理了这些情况,显然更不容易出错。

      此外,像boost::shared_ptr 这样的智能指针甚至可以处理自定义释放函数(默认情况下它使用delete)。在实践中,我很少遇到使用智能指针而不是原始指针不切实际的情况。

      简单说明一下:boost 智能指针类是仅标头设计的(基于模板),因此它们不需要额外的依赖项。 (有时,这很重要)您可以只包括它们,一切都应该没问题。

      【讨论】:

      • 在这里使用shared_ptr 意味着该对象的所有副本共享所指向对象的相同实例。这可能不是 OP 想要的。
      • @Space_C0wb0y,你说得对。然而,即使他希望每个实例都有自己的副本,使用智能指针仍然不太容易出错。
      • this.pointer ??对我来说看起来不像 C++。该解释还遗漏了另一个关键的 C++ 概念,即异常安全。如果new 抛出,代码可能会崩溃,并且this->pointer 在dtor 中第二次被删除。
      • @MSalters:这显然是一个错字。我最近做了太多C# ;) 另外,我的回答是显示 one 可能和 common 错误。当然异常安全很重要,是的,你是对的;我更新了我的答案。谢谢。
      • 我必须深入挖掘 Sutter 的确切文章,但他表明,一旦您拥有异常安全性,安全的自我分配通常会产生副作用。这是因为您通常会从“分配新,解除分配旧”的顺序中获得异常安全性。这意味着您不需要自我分配。它也非常罕见,因此这些检查也效率低下。因此,在某些情况下,应该建议关注异常安全,而不是为自我分配而烦恼。
      【解决方案4】:

      虽然我同意其他人的说法,您应该将指针包装在其自己的 RAII 类中,并让编译器综合复制构造函数、析构函数和赋值运算符,但有一种方法可以解决您的问题:声明(并定义)私有静态函数它将为不同的构造函数做任何需要和常见的事情,并从那里调用它。

      【讨论】:

        【解决方案5】:

        根据经验:如果您必须手动管理资源,将每个资源包装到自己的对象中。

        使用适当的复制构造函数将char* 放入它自己的对象中,并让编译器为A 执行复制构造函数。 请注意,这也涉及分配和销毁,您在问题中没有提到,但仍然需要处理。
        标准库有几种类型可供选择,其中std::stringstd::vector<char>

        【讨论】:

        【解决方案6】:

        您可以将您的可复制成员分成一个 POD 结构,并单独维护需要托管副本的成员。

        由于您的数据成员是私有的,因此您班级的客户可能看不到它。

        例如

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

        【讨论】:

          【解决方案7】:

          问题是,你的类中真的需要一个具有深拷贝语义的指针吗?根据我的经验,答案几乎总是否定的。也许您可以解释一下您的情况,以便我们向您展示替代解决方案。

          也就是说,this article 描述了具有深拷贝语义的智能指针的实现。

          【讨论】:

            【解决方案8】:

            始终使用 RAII 对象来管理非管理资源,例如原始指针,并为每个资源使用一个 RAII 对象。一般避免使用原始指针。在这种情况下,使用std::string 是最好的解决方案。

            如果由于某种原因无法做到这一点,请将易于复制的部分考虑到基类或成员对象中。

            【讨论】:

              【解决方案9】:

              char* 替换为std::string

              【讨论】:

              • 我认为 char* p 只是类中任何指针在复制 c'tor 中需要特殊处理的一个示例。
              • @Gene Vincent:那么 OP 应该在问题中这么说。在这种情况下,应该编写一个专用的 RAII 类来管理指针。
              • @Philipp: ...或者选择一个合适的现成的。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2013-06-02
              • 2013-04-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-15
              相关资源
              最近更新 更多