【问题标题】:Can a copy-constructor take a non-const parameter?复制构造函数可以采用非常量参数吗?
【发布时间】:2012-07-27 08:13:19
【问题描述】:

我有这个问题,有一个函数foo()如下,

vector<ClassA> vec;

void foo()
{
    ClassA a;   //inside foo, a ClassA object will be created
    a._ptr = new char[10];

    vec.push_back(a);   //and this newly created ClassA object should be put into vec for later use
}

而 AFAIK,vec 将调用 ClassA 的 copy-ctor 来复制新创建的对象 a,这就是问题所在。如果我以通常的方式定义ClassA的copy-ctor,

ClassA::ClassA(const ClassA &ra) : _ptr(0)
{
    _ptr = ra._ptr;
}

然后对象a及其副本(由vec创建)将有指针_ptr指向同一区域,当foo完成时,a将调用析构函数释放_ptr,然后@987654333 @ 在vec 中的副本将是一个悬空指针,对吧?由于这个问题,我想这样实现ClassA的copy-ctor,

ClassA::ClassA(ClassA &ra) : _ptr(0) //take non-const reference as parameter
{
    std::swap(_ptr, a._ptr);
}

我的实现好吗?或者任何其他方式可以帮助完成这项工作?

【问题讨论】:

  • 请不要重新发明std::****_ptr ;)
  • 为什么需要存储指向char的指针?不使用std::stringvector&lt;char&gt; 会避免问题吗?

标签: c++


【解决方案1】:

回答您的名义问题:是的,任何具有 T &amp;T const &amp; 类型的强制参数(它还可能有更多默认参数)的类 T 的构造函数都是复制构造函数。在 C++11 中,还有一个 move 构造函数,它需要一个 T &amp;&amp; 类型的参数。

拥有一个实际改变参数的非常量复制构造函数会给你的类带来非常不寻常的语义(通常是“转移语义”),并且应该被广泛记录;它还可以防止您复制常量(显然)。旧的std::auto_ptr&lt;T&gt; 正是这样做的。

如果可能的话,新的 C++11 风格的可变右值引用和移动构造函数为原始对象中不再需要资源时“移动”资源的问题提供了更好的解决方案。这是因为右值引用是对 mutable 对象的引用,但它只能绑定到“安全”表达式,例如临时变量或您已显式转换(通过std::move)并因此标记的事物作为一次性用品。

【讨论】:

    【解决方案2】:

    C++11 为此目的引入了移动构造函数:

    ClassA::ClassA(ClassA&& ra)
    : _ptr(ra._ptr)
    {
        ra._ptr = nullptr;
    }
    

    您也可以将_ptr 声明为共享指针:

    std::shared_ptr<char[]> _ptr;
    

    然后默认的 denerated 复制构造函数就可以了。

    【讨论】:

    • 可悲的是,我没有使用 C++11
    • shared_array,不是shared_ptr :)
    • @timdum shared_array 来自 boost,在 std 中只有 shared_ptr,但 i 专门用于数组。
    • @Alcott:即使没有 C++11,您仍然可以通过 Boost.Move 获得移动构造函数(以及一般的移动语义)的许多优点。
    • 忽略我之前的评论,std::shared_ptr is not specialised,必须在构造时明确提供删除器
    【解决方案3】:

    你不应该复制指针,你应该复制指针指向的上下文。你还应该通过告诉它你想要多少元素来初始化类,而不是通过访问公共指针来分配它。

    由于您要复制对象,而不是移动它,因此您应该在复制时在新对象中分配资源。

    class A {
    
            int* p_;
            int size_;
    public:
    
            A(int size) 
            : p_(new int[size]()), 
            size_(size) {
            }
    
            A(const A &a) 
            : p_(new int[a.size_]),
             size_(a.size_) {               
                    std::copy(a.p_, a.p_ + a.size_, p_);
            }
    
            ...
    
    };
    
    
    int main () {
            A a(10);
            vec.push_back(a);
    }
    

    但是,如果您知道要复制的对象在复制后未被使用,则可以移动它的资源。

    【讨论】:

    • 但他想将原始对象中的指针设为NULL,例如更改所有者
    • 他说“如果我以通常的方式定义复制 ctor”。这不是定义复制 ctor 的常用方法。没有提到他想要移动它,所以我假设他想要一个正确的复制 ctor。
    • 是的,但他还继续解释为什么这样做不会,这是因为需要深拷贝,这是您在此处解决的问题。如果您阅读更多内容并尝试解释他编写的代码,您会发现他正在尝试将指针从原始类交换到新类,从而将原始指针设置为 NULL 并将新的 copy:s 指针设置为旧指针地址。这就是为什么他不想使用带有 const 参数的标准复制构造函数。
    • @Bakery 这就是移动构造函数的用途。复制构造函数不能使旧对象无效,并且不应在对象之间共享的资源在调用复制构造函数后仍应分开。
    【解决方案4】:

    你的实现的问题是你不能将临时对象作为参数传递给这个拷贝ctor(临时对象总是const)。就像已经提到的那样,最好的解决方案是迁移到 c++11 并使用移动语义。如果不可能,shared_array 可以替代。

    【讨论】:

      【解决方案5】:

      补充说明:

      避免此类问题使用new 创建对象并将指向该对象的指针存储在向量中。

      【讨论】:

      • 我不认为尝试通过更明确的内存管理来解决内存管理问题是一个好主意。动态分配对象只是为了解决损坏的复制构造函数是一个不好的建议。
      猜你喜欢
      • 1970-01-01
      • 2023-03-20
      • 2012-12-02
      • 1970-01-01
      • 1970-01-01
      • 2014-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多