【问题标题】:Questions about a Segmentation Fault in C++ most likely caused by a custom copy constructor关于 C++ 中最可能由自定义复制构造函数引起的分段错误的问题
【发布时间】:2011-08-25 16:26:58
【问题描述】:

我遇到了分段错误,我认为这是由复制构造函数引起的。但是,我在网上的任何地方都找不到这样的例子。我读过浅拷贝和深拷贝,但我不确定这个拷贝属于哪个类别。有人知道吗?

MyObject::MyObject{
    lots of things including const and structs, but no pointers
}
MyObject::MyObject( const MyObject& oCopy){
    *this = oCopy;//is this deep or shallow?
}
const MyObject& MyObject::operator=(const MyObject& oRhs){
    if( this != oRhs ){
        members = oRhs.members;
        .....//there is a lot of members
    }
    return *this;
}
MyObject::~MyObject(){
    //there is nothing here
}

代码:

const MyObject * mpoOriginal;//this gets initialized in the constructor

int Main(){
    mpoOriginal = new MyObject();
    return DoSomething();
}

bool DoSomething(){
    MyObject *poCopied = new MyObject(*mpoOriginal);//the copy
    //lots of stuff going on
    delete poCopied;//this causes the crash - can't step into using GDB
    return true;
}

编辑:添加 operator= 和构造函数

已解决:树错了树,结果是一个函数在同一个对象上调用了两次 delete

【问题讨论】:

  • operator=(const MyObject&) 是什么样的?
  • 您能否提供一个完整 可编译示例来演示该问题?如果需要,消除“发生的很多事情”,它可能与问题相关,也可能不相关。
  • 谁一直支持不合标准的问题?我总是能看到它。有人在尝试快速获得徽章吗?
  • 我已经添加了overator=,就像我想发布所有我认为我的雇主不会高兴的代码一样。加上大约 5,000 行
  • 5,000 行 operator=!好伤心。那就是你的问题。但是要回答您的原始问题,您的复制构造函数具有赋值运算符的语义,因此要判断它是浅的还是深的,您需要查看赋值运算符代码。但我不认为这是你真正的问题,真正的问题是你的课程一团糟。

标签: c++ segmentation-fault copy-constructor deep-copy shallow-copy


【解决方案1】:

在复制构造函数中使用这样的赋值运算符通常是一个坏主意。这将默认构建所有成员,然后分配给他们。要么只依赖隐式生成的复制构造函数,要么使用成员初始化器列表复制那些需要复制的成员,并将适当的初始化应用于其他成员。

没有类成员的详细信息,很难判断是什么导致了您的段错误。

【讨论】:

  • 对不起,代码中有一个 operator=,我不知何故忽略了它。我已将其添加到伪代码中
  • 您发布的大纲operator= 建议按成员分配,在这种情况下,默认分配运算符可以正常工作,并避免需要这 5000 行。但是,详细信息取决于成员的类型,以及是否有任何成员从列表中省略,但不应该。
【解决方案2】:

根据您的代码,您不是在创建原始对象...您只是在创建一个像这样的指针: const MyObject * mpoOriginal;

所以副本正在使用坏数据到创建的新对象中......

【讨论】:

  • OP 的代码 sn-p 中的注释说“这在构造函数中被初始化”...
  • @Oli:由于他通过*mpoOriginal 进行构造,因此唯一可行的方法是通过复制构造函数。但是没有什么可复制的!这对我来说似乎是直接正确的答案 (+1),尽管这段代码还有很多其他问题,而且 仍然 很高兴真正看到有效代码(而不是“这里的东西” ,我保证它有效!”)。
  • @Oli:但该评论非常模棱两可。并且从示例中省略了足够多的东西,很难知道 OP 的代码中什么是 OK 或不是。我们真正知道的是,OP 正在调用 copy-ctor 中的赋值复制运算符。几乎所有其他事情都是猜测。
  • 他可能正在初始化*mpoOriginal 的成员,但他几乎可以肯定没有为他们分配任何空间。
  • mpoOriginalMain() 中初始化为new MyObject
【解决方案3】:

哇....

MyObject::MyObject( const MyObject& oCopy)
{
    *this = oCopy;//is this deep or shallow?
}

两者都不是。这是对赋值运算符的调用。
由于您尚未完成对象的构造,因此这可能是不明智的(尽管完全有效)。不过,更传统的做法是根据复制构造函数定义赋值运算符(请参阅复制和交换 idium)。

const MyObject& MyObject::operator=(const MyObject& oRhs)
{
    if( this != oRhs ){
        members = oRhs.members;
        .....//there is a lot of members
    }
    return *this;
}

基本上没问题,虽然通常分配的结果不是续的。
但是如果你这样做,你需要稍微划分你的处理以使其异常安全。它应该看起来更像这样:

const MyObject& MyObject::operator=(const MyObject& oRhs)
{
    if( this == oRhs )
    {
        return *this;
    }
    // Stage 1:
    // Copy all members of oRhs that can throw during copy into temporaries.
    // That way if they do throw you have not destroyed this obbject.

    // Stage 2:
    // Copy anything that can **not** throw from oRhs into this object
    // Use swap on the temporaries to copy them into the object in an exception sage mannor.

    // Stage 3:
    // Free any resources.
    return *this;
}

当然,还有一种更简单的方法是使用复制和交换 idum:

MyObject& MyObject::operator=(MyObject oRhs)  // use pass by value to get copy
{
    this.swap(oRhs);
    return *this;
}

void MyObject::swap(MyObject& oRhs)  throws()
{
    // Call swap on each member.
    return *this;
}

如果在析构函数中没有什么可做的,不要声明它(除非它需要是虚拟的)。

MyObject::~MyObject(){
    //there is nothing here
}

这里你声明了一个指针(不是一个对象),所以构造函数不会被调用(因为指针没有构造函数)。

const MyObject * mpoOriginal;//this gets initialized in the constructor

在这里你调用 new 来创建对象。
你确定你要这么做吗?必须销毁动态分配的对象;表面上是通过删除,但更常见的是在 C++ 中,您将指针包装在智能指针中,以确保所有者正确并自动销毁对象。

int main()
{ //^^^^   Note main() has a lower case m

    mpoOriginal = new MyObject();
    return DoSomething();
}

但是因为您可能不想要动态对象。您想要的是在超出范围时被销毁的自动对象。此外,您可能不应该使用全局变量(将其作为参数传递,否则您的代码正在使用与全局状态相关的副作用)。

int main()
{
     const MyObject  mpoOriginal;
     return DoSomething(mpoOriginal);
}

您不需要调用 new 来制作副本,只需创建一个对象(传递您要复制的对象)。

bool DoSomething(MyObject const& data)
{
    MyObject   poCopied (data);     //the copy

    //lots of stuff going on
    // No need to delete.
    // delete poCopied;//this causes the crash - can't step into using GDB
    // When it goes out of scope it is auto destroyed (as it is automatic).

    return true;
}

【讨论】:

    【解决方案4】:

    您正在做的是让您的复制构造函数使用赋值运算符(您似乎没有定义)。坦率地说,我很惊讶它可以编译,但是因为您没有显示所有代码,也许它可以编译。

    用正常的方式编写你的复制构造函数,然后看看你是否仍然遇到同样的问题。如果你所说的“很多事情......但我没有看到任何指针”是真的,那么你根本不应该编写复制构造函数。尝试删除它。

    【讨论】:

      【解决方案5】:

      对于导致段错误的确切原因,我没有直接答案,但这里的传统观念是遵循rule of three,即当您发现自己需要任何的复制构造函数时,赋值运算符或析构函数,您最好实现所有三个c++0x 添加移动语义,这使它成为“四规则”?)。

      然后,通常情况正好相反——复制赋值运算符是根据复制构造函数实现的——copy and swap idiom

      【讨论】:

      • 我认为 C++11 使它成为“五法则”:-)
      • 谢谢,我已经编辑了帖子以包含 operator= 代码,它在那里,我只是在发布时忽略了它
      【解决方案6】:
      MyObject::MyObject{
          lots of things including const and structs, but no pointers
      }
      

      浅拷贝和深拷贝的区别只有在有指向动态内存的指针时才有意义。如果这些成员结构中的 any 没有对其指针进行深层复制,那么您将不得不解决这个问题(如何取决于结构)。但是,如果所有成员要么不包含指针,要么正确地对其指针进行深拷贝,那么复制构造函数/赋值就不是问题的根源。

      【讨论】:

        【解决方案7】:

        两者都可以,这取决于您的 operator= 所做的事情。这就是魔法发生的地方;复制构造函数只是在调用它。

        如果您自己没有定义operator=,那么编译器会为您合成一个,它正在执行浅拷贝。

        【讨论】:

        • 但是,如果他说他有 const 成员是正确的,那么编译器就不可能合成赋值运算符。这就是让我困惑的地方。
        • 根据给出的代码,MyObject 的析构函数不会释放任何指针成员。如果这是真的,“//很多事情正在发生”位更有可能以某种方式使 poCopied 指针无效。
        • @john:他在哪里说他有const 成员?
        • @Tomalak:好吧,他并没有准确地说出来,我在上面的代码中解释了他的话“很多东西,包括 const - 但我没有看到任何指针”。当然我可能是错的。
        • @john:那是在 ctor 正文中而不是成员初始化器中,所以我认为他不是指成员。再说一次,他的 ctor 甚至没有参数列表。在他真正向我们展示一些 C++ 之前,我们中的任何一个都无法确定。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-27
        • 1970-01-01
        • 2010-10-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多