C++复制构造函数:尝试引用已删除的函数

5
我有一个名叫classA的类,大概长这样:
class classA {
    private:        
        char* data; 
    public:
        classA(const classA&) = delete;
        ~classA();      
    };

~classA()
    {
        delete[] data;
    }

在另一个类中,我们称之为classB,我作为成员拥有对classA的共享指针:

class classB
    {
    private:
        std::shared_ptr<classA> ptrA;
    public: 
        classB(std::shared_ptr<classA>);
    };

classB(std::shared_ptr<classA> sp) : ptrA(sp)
    {}

这是我实例化classB的方法:
classA ca;
classB cb(std::make_shared<classA>(ca));

这给我返回了以下错误信息:
尝试引用已删除的函数。
显然,我正在尝试引用被定义为“已删除”的复制构造函数(这是有原因的,此类对象不应被复制)。但我困惑的是,既然我正在传递共享指针,为什么会调用复制构造函数,以及如何避免这种情况。

你怎么怀疑共享指针是如何构造的? - kmdreko
请注意,如果您执行了std::make_shared<classA>(std::move(ca)),仍然会出现问题,请参见Default move constructor/assignment and deleted copy constructor/assignment - Justin
1
你应该展示实际的代码,你的代码不能有那个问题。 - Slava
2
make_shared 正试图从另一个 classA (ca) 构造一个 classA。你想做什么?你希望 shared_ptr 接管 ca 的所有权吗? - Tas
@Slava 虽然 classA ca(); 确实是前向声明了一个名为 ca 的函数,该函数返回一个 classA,但我认为我们都知道 OP 实际上想表达的意思。当然,他们没有发布实际的代码,但仍然足够清楚。 - Justin
显示剩余2条评论
2个回答

6

你正在调用复制构造函数,试图创建共享指针。

std::make_shared<classA>(ca)
                         ^^ constructs the classA using these parameters

您可以调用 make_shared<classA>() 来创建一个指向默认构造的 classA 的共享指针。或者选择其他构造函数。


如果我有一个带参数的构造函数,比如classA(int n),在创建共享指针时如何选择这个构造函数而不是默认的构造函数? - Eutherpy
简单地将所需的构造函数参数直接传递给std::make_shared(),例如:std::make_shared<classA>(12345)。就像您将ca传递给std::make_shared()以调用复制构造函数一样。 - Remy Lebeau
你可以像这样传递参数到 make_sharedstd::make_shared<classA>(someInt) - kmdreko
我尝试了 auto x = make_shared<classA>(some_integer_value),然后 classB cb(x);,但是这给了我另一个错误:classA (classA &&)': 无法将参数1从 'std::shared_ptr<classA>' 转换为 'const classA&'。 - Eutherpy
错误“*无法将参数1从'std :: shared_ptr<classA>'转换为'const classA&'*”表示classB的构造函数期望输入一个const classA&,而不是std :: shared_ptr<classA>。将classB更改为接受std :: shared_ptr<classA>(这样如果xclassB仍在使用classA时超出范围,则无法销毁classA),否则调用std :: shared_ptr :: operator *,例如:classB cb(* x);并确保在cb之前x不会超出范围。 - Remy Lebeau
请考虑提出另一个问题,因为这似乎与您上面发布的内容不符。 - kmdreko

4
这个例子可以简化很多。
#include <memory>

class classA {
 public:
  classA(const classA&) = delete;
  classA() = default;
};


int main() {
  classA ca; // note, no ()
  auto sp = std::make_shared<classA>(ca); // problem.
  auto sp2 = std::make_shared<classA>();  // this does what you want
}

你正在将ca作为参数传递给std::make_shared,它通过调用你传递给make_shared的任何参数来构造一个classA,并且调用classA::classA
如果你考虑一下make_shared的实现方式,这可能更加明显。
template <typename Cls, typename... Args>
std::shared_ptr<Cls> MakeShared(Args&&... args) {
                                     //this is where the copying happens
  return std::shared_ptr<Cls>{new Cls{std::forward<Args>(args)...}};
}

int main() {
  classA ca;
  auto sp = MakeShared<classA>(ca);
  auto sp2 = MakeShared<classA>();
}

你将ca传递给MakeShared,然后MakeShared调用new Cls(...),其中的...是你传递给MakeShared的任何内容,在这种情况下,是另一个classA对象。
如果上面的内容过于密集(也许你不习惯使用forward或可变参数模板),那么考虑这个简化版本的MakeShared,它对你的问题案例做出了相同的处理。
template <typename Cls, typename Arg>
std::shared_ptr<Cls> MakeShared(const Arg& arg) {
                      // copy here ----+
                      //               v
  return std::shared_ptr<Cls>{new Cls{arg}};
}

int main() {
  classA ca;
  auto sp = MakeShared<classA>(ca);
}

感谢您的详细解释! - Eutherpy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接