【问题标题】:Return object with 2 optional ctors in function在函数中返回具有 2 个可选 ctor 的对象
【发布时间】:2018-11-10 16:24:00
【问题描述】:

我在 c++ 中有一个函数,其中包含我构建的对象的 2 个可选 c'tor(一个带有向量“vals”中的东西,其他没有)。

...
    RecievedMessage a(sc, type);
    if (!vals.empty()){
        //a.~RecievedMessage();
        RecievedMessage a(sc, type, vals);
    }
    return &a;
}

//中的行是可选的。

它会工作吗(有或没有可选行)?为什么?如果没有,如何在没有“vals”的设置器的情况下修复它? 非常感谢。

【问题讨论】:

  • 不能返回指向本地对象的指针
  • 这绝不是需要显式调用析构函数的情况。
  • if 分支中的a 与外部分支中的a 无关。它不会替换它或覆盖它。它只是 隐藏 作用域的剩余部分(if 分支)。
  • "a.~RecievedMessage();" - 我能想到的唯一可以显式调用析构函数的情况是通过放置 new 和类似方法管理某个内存池中的对象时。多次调用对象的析构函数是未定义的行为,如果 you 显式调用它,然后编译器在超出范围时再次调用它,则 bam;前往 UB 土地的单程票。
  • return &a; 将返回局部变量的地址 -> 悬空指针。 (或者你可能超载operator &...)

标签: c++ function object constructor return


【解决方案1】:

不,这行不通。

    RecievedMessage a(sc, type);
// Here we construct 'a'
    if (!vals.empty()){
        //a.~RecievedMessage();
// If we enable this line, we destroy 'a'
        RecievedMessage a(sc, type, vals);
// Here we construct a second 'a' that only exists in this block
    }
// End of block: The inner 'a' is destroyed here automatically
    return &a;
}
// End of block: The outer 'a' is destroyed here, again.

两次销毁一个对象具有未定义的行为。你不想这样。

如果不手动调用析构函数,那么外层a只会被销毁一次,这样就好了。

但在任何一种情况下,RecievedMessage a(sc, type, vals); 都与外部 a 无关,只是创建了另一个变量。

有办法解决这个问题,但代码的最后一行让这一切变得毫无意义:

    return &a;

您正在返回一个局部变量的地址。这本身就被破坏了:当函数返回时,它的所有局部变量都会自动销毁,所以你返回的是一个无效的指针。

【讨论】:

  • 可能 OP 过载 operator &... ;-)
  • @Jarod42 考虑到范围/阴影的混淆,我发现这不太可能。 :-)
【解决方案2】:

您的代码到处都是,但我认为您正在寻找的是这样的:

ReceivedMessage *MakeReceivedMessage (foo sc, bar type, vector<whatever>& vals)
{
    if (vals.empty())
        return new ReceivedMessage (sc, type);

    return new ReceivedMessage (sc, type, vals);
}

当然,在这个例子中最好只有一个构造函数,并在适当的时候让对象测试vals 是否为空,但是,一般来说,你可以随时调用任何你喜欢的构造函数。只需正确管理您的对象生命周期(并且永远不要返回指向堆栈上对象的指针)。

示例用法(管理正确返回的对象的生命周期):

std::unique_ptr<ReceivedMessage> MyReceivedMessage (MakeReceivedMessage (...));
MyReceivedMessage->DoFunkyStuffWithMessage ();
....

或者,正如 melpomene 指出的那样,您可以首先返回 std::unique_ptr&lt;ReceivedMessage&gt;。一些(很多?)会更喜欢那个。您可以使用std::make_unique 构建它。

【讨论】:

  • 一般不推荐返回原始指针(因为混淆了对象所有权)。我认为当前的最佳做法是返回std::unique_ptr&lt;ReceivedMessage&gt;
  • @melpomene 当然,但是 OP 已经够混乱了,我不想投入太多。这可能比使用代码原样执行 std::unique_ptr&lt;ReceivedMessage&gt; x (MakeReceivedMessage (...)); 稍微贵一点.看个人喜好吧,我猜。我会用一个例子来编辑帖子。
  • @melpomene 想了更多。我更喜欢返回一个原始指针,因为调用者可能希望将其存储在std::shared_ptr 中,而不一定是std::unique_ptr。在 C++ 中,总是可能会搞砸内存管理,过多的手动操作只会让情况变得更糟。
【解决方案3】:

您的代码目前存在三个主要问题:

首先,您对析构函数~ReceivedMessage() 的注释掉调用根本不应该存在。在 C++ 中,对象的析构函数会在对象的生命周期结束时自动调用(当它超出范围时,或者如果使用 new 动态分配,则在调用 delete 时)。虽然在某些情况下需要显式调用析构函数(例如“placement new”),但您不太可能遇到这些情况。

其次,您在内部if 中的RecievedMessage a(sc, type, vals); 声明不会替换外部范围中a 的值。这只是创建了另一个同名变量,它隐藏了外部a,而外部范围内的return &amp;a; 只能引用外部a。内部的a 此时不再存在,因为它已超出范围。

解决此问题的一种方法是通过使用= 运算符并构造一个临时的ReceivedMessage 来为a 分配一个新值:

if (!vals.empty()) {
    a = ReceivedMessage(sc, type, vals);
}

只要为ReceivedMessage 定义了正确的operator=(隐式或以其他方式),这应该可以工作。

第三,你的函数返回一个指向局部变量a的指针。由于 C++ 中的对象一超出范围就会被销毁,因此在函数返回时 a 不再存在,因此调用代码获得的 ReceivedMessage * 指针无效,取消引用该指针将是未定义的行为指针并加以利用。

有几个解决这个问题的方法:

第一个选项不是返回指针 (ReceivedMessage *),而是按值返回 ReceivedMessage

ReceivedMessage foo()
{
    ReceivedMessage a(123);

    return a;
}

只要为ReceivedMessage 定义了正确的复制或移动构造函数(隐式或其他方式),这应该可以工作。

第二个选项是使用std::unique_ptr,并让你的函数返回std::unique_ptr&lt;ReceivedMessage&gt;

#include <memory>

std::unique_ptr<ReceivedMessage> foo()
{
    std::unique_ptr<ReceivedMessage> a;

    if (vals.empty()) {
        a = std::make_unique<ReceivedMessage>(sc, type);
    } else {
        a = std::make_unique<ReceivedMessage>(sc, type, vals);
    }

    return a;
}

这种方法的优点是unique_ptr 可以为空,因此您可以创建一个空unique_ptr,而无需立即构造一个ReceivedMessage。此外,您可以安全地移动和分配 unique_ptr 值,而无需定义正确的 operator= 或正确的复制/移动构造函数。

使用unique_ptr时,调用代码可能如下所示:

std::unique_ptr<ReceivedMessage> message = foo();
foo->bar();

与直接使用ReceivedMessage 时相反:

ReceivedMessage message = foo();
foo.bar();

【讨论】:

    猜你喜欢
    • 2020-09-10
    • 1970-01-01
    • 2019-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-23
    • 1970-01-01
    • 2019-05-26
    相关资源
    最近更新 更多