从重载的复制构造函数中调用默认的复制构造函数

时间:2011-09-04 02:17:58

标签: c++ constructor shared-ptr copy-constructor deep-copy

我需要编写一个复制构造函数,深度复制std::shared_ptr的内容。但是,在类中也定义了一堆变量int a, b, c, d, e;。有没有办法在我新的重载代码中生成默认的复制构造函数代码(或调用默认的复制构造函数)。

以下是一个带有评论的代码段,希望能够澄清问题。

class Foo {
public:
     Foo() {}
     Foo(Foo const & other);
     ...
private:
     int a, b, c, d, e;
     std::shared_ptr<Bla> p;
};

Foo::Foo(Foo const & other) {
    p.reset(new Bla(*other.p));

    // Can I avoid having to write the default copy constructor code below
    a = other.a;
    b = other.b;
    c = other.c;
    d = other.d;
    e = other.e;
}

7 个答案:

答案 0 :(得分:7)

我一直认为像这样的问题应该至少有一个答案引用未来读者的标准,所以在这里。

该标准的第12.8.4节规定:

  

如果类定义没有显式声明复制构造函数,则会隐式声明一个。

这意味着当类定义 显式声明一个复制构造函数时,一个隐式声明。因此,如果您明确声明一个,则隐式的不存在,因此您无法调用它。

答案 1 :(得分:5)

这是问题代码,因为我正在写这个:

class Foo {
public:
     Foo() {}
     Foo(Foo const & other);
     ...
private:
     int a, b, c, d, e;
     std::shared_ptr<Bla> p;
};

Foo::Foo(Foo const & other) {
    p.reset(new Bla(other.p));

    // Can I avoid having to write the default copy constructor code below
    a = other.a;
    b = other.b;
    c = other.c;
    d = other.d;
    e = other.e;
}

以上代码很可能错误,因为

  1. 默认构造函数离开abcde未初始化,

  2. 代码不负责分配复制,

  3. 表达式new Bla(other.p)要求Bla的构造函数采用std::shared_ptr<Bla>,这种情况极不可能。

  4. 使用std::shared_ptr这必须是C ++ 11代码才能在语言上正式正确。但是,我相信它只是使用编译器可用的代码。所以我认为相关的C ++标准是C ++ 98,并修改了C ++ 03修正案。

    即使在C ++ 98中,您也可以轻松利用内置(生成)的复制初始化,例如

    namespace detail {
        struct AutoClonedBla {
            std::shared_ptr<Bla> p;
    
            AutoClonedBla( Bla* pNew ): p( pNew ) {}
    
            AutoClonedBla( AutoClonedBla const& other )
                : p( new Bla( *other.p ) )
            {}
    
            void swap( AutoClonedBla& other )
            {
                using std::swap;
                swap( p, other.p );
            }
    
            AutoClonedBla& operator=( AutoClonedBla other )
            {
                other.swap( *this );
                return *this;
            }
        };
    }
    
    class Foo {
    public:
         Foo(): a(), b(), c(), d(), e(), autoP( new Bla ) {}
         // Copy constructor generated by compiler, OK.
    
    private:
         int                      a, b, c, d, e;
         detail::AutoClonedBla    autoP;
    };
    

    请注意,此代码在默认构造函数中正确初始化,确实负责复制分配(使用swap idiom),并且不需要特殊的智能指针识别Bla构造函数,而只是使用普通的Bla复制构造函数来复制。

答案 2 :(得分:4)

shared_ptr上编写一个内置深度复制的变体会更容易。这样,你必须为你的主类写一个拷贝构造函数;仅适用于此特殊deep_copy_shared_ptr类型。你的deep_copy_shared_ptr会有一个拷贝构造函数,它会存储一个shared_ptr本身。它甚至可以隐式转换为shared_ptr,以使其更容易使用。

答案 3 :(得分:1)

据我所知,但你可以(而且应该)做的是使用初始化列表,无论如何:

Foo::Foo(Foo const & other)
    : a(other.a), b(other.b), c(other.c), 
      d(other.d), e(other.e), p(new Bla(other.p))
{}

这不会让你免于写作,但它可以避免分配(不必要的)默认构造成员(尽管在这种情况下它可能没问题)可能造成的性能损失以及这可能带来的许多其他陷阱。如果可能,请始终在构造函数中使用初始化列表。

顺便说一下,Kerrek的评论是正确的。如果您进行深层复制,为什么还需要shared_ptr。在这种情况下,unique_ptr可能更合适。除了它的好处shared_ptr不是一般的不再考虑解除分配解决方案,你应该总是考虑是否需要一个智能指针以及哪种类型的智能指针是最合适的。

答案 4 :(得分:1)

那是不可能的。您可以编写自定义复制构造函数(完全由您自己编写),也可以编译器为您编写。

请注意,如果您编写复制构造函数,那么您可能还需要复制赋值和析构函数,因为编写这三个资源管理函数中的任何一个都意味着您正在管理资源。但是,使用复制和交换习惯用法,您只需要在复制构造函数中编写一次复制逻辑,然后根据复制构造函数定义赋值运算符。

除此之外,我不完全确定你为什么使用shared_ptr<>shared_ptr<>的要点是允许多个指针安全地指向同一个对象。但是你没有分享指针,你会对它进行深层复制。也许您应该使用原始指针,并在析构函数中释放它。或者,更好的是,用shared_ptr<>替换clone_ptr,然后完全删除复制构造函数,复制赋值和析构函数。

答案 5 :(得分:0)

默认赋值运算符与默认复制构造函数具有相同的代码。虽然您无法从重载中调用默认的复制构造函数,但是在assignemnt中存在相同的代码。

Foo::Foo(Foo const & other) 
{
    *this = other;
    p.reset(new Bla(other.p));
}

应该能满足你的需求。

编辑:没关系它实际上是相同的代码,并明确声明副本construtor阻止它生成:(

答案 6 :(得分:0)

一个技巧是将那些“简单”的字段放入基类

class FooBase {
protected:
    int a, b, c, d, e;
};

class Foo : public FooBase {
public:
     Foo() {}
     Foo(const Foo& other)
       : FooBase(other),         // This copies all the simple fields
         p(new Bla(other.p))     // Manually do the hard part
     {}
     ...
private:
     std::shared_ptr<Bla> p;
};

然后,如果您向 Foo 添加任何简单字段,则可以将它们放入 FooBase,而无需担心更新复制构造函数。

如果需要,不要忘记添加类似的复制赋值运算符。在现代编译器上,还添加了移动构造函数和移动赋值运算符。这两个可能是默认的,因为您不需要移动操作的深层复制。