【问题标题】:RVO when converting return value转换返回值时的 RVO
【发布时间】:2020-11-15 23:10:18
【问题描述】:

我为转换和转换的复杂性而苦苦挣扎,我在网上找不到明确保证函数返回时有效转换的建议。我有两个类,Base 和 Derived,其中 Derived 在 Base 上没有额外的数据成员。我有一个用于基类的命名构造函数,我想使用 RVO 返回并以尽可能少的开销强制转换为派生类型的对象。

class Base { 
public:
    static Base namedConstructor(int n){
        return Base(n);
    } 
protected:
    Base(int n) : member(n){
    }
    int member; 
};

class Derived : public Base  {
    static Derived nC2(int n) {
        Derived derived = namedConstructor(n); 
        // Error: no suitable user-defined conversion from "Base" to "Derived"... 
        // modify derived
        return derived; 
    } 
};

有没有办法修复满足以下所有要求的错误?

  1. 不使用 RTTI。动态转换似乎没有必要。
  2. namedConstructor 的定义只有一个,以防我需要修改它。如有必要,我可以将其设为模板,但我对替代方案很感兴趣。
  3. namedConstructor 应该利用 nC2 中的 RVO。
  4. 如果我在基类或派生类中添加或删除数据成员,转换不应无声地失败(类似于 reinterpret_cast 的操作可能会这样做)。

【问题讨论】:

  • 你不能像这样将一个对象转换成另一个对象。编译器需要知道如何从另一个创建一个。 Derived 对象甚至无法构造,因为 Base 有一个私有构造函数。
  • 我使构造函数受到保护。我不确定如何解决您的其他问题。当然不能这样转换和反对,有错误。

标签: c++ inheritance type-conversion return-value-optimization


【解决方案1】:

添加一个private ctor。到Derived,它从Base构造它:

Derived(const Base& base) : Base(base) {
}

如果稍后您将额外的成员变量添加到 Derived,请同时初始化它们。

这是符合标准的(虽然有点奇怪),并将在发布版本中进行优化。

【讨论】:

  • 谢谢,但是 RVO 在“Derived derived = namedConstructor(n);”行中是如何工作的?假设 base 有一个由 n 个 0 组成的大数组数据成员。在“Derived derived = namedConstructor(n);”行中,该大数组将被写入堆栈或堆多少次?你怎么知道的?
  • @cinnamon “对象直接构建到存储中,否则它们将被复制/移动到。”如果存在复制省略,则意味着一次,直接在目标存储上。
  • @cinnamon:代码优化得非常好,见demo。 MSVC 只调用nC2,clang 和 gcc 优化得更好。
【解决方案2】:

我添加了构造函数,并且在 g++ 3.0 中存在复制省略:

class Base { 
public:
    static Base namedConstructor(int n){
        return Base(n);
    } 
protected:
    Base(int n) : member(n){
        std::cout << "Create Base" << std::endl;
    }
    Base (const Base & b) : member (b.member) {
        std::cout << "Copy Base" << std::endl;
    }
    int member; 
};

class Derived : public Base  {
public:
    Derived () : Base (0) {
        std::cout << "Create Derived" << std::endl;
    }
    Derived (const Base & b) : Base(b) {
        std::cout << "Create Derived from base" << std::endl;
    }
    int val () { return member; }
    static Derived nC2(int n) {
        return namedConstructor(n); //derived; 
    } 
};

int main ()
{
    Derived d = Derived::nC2(1);
    std::cout << "Value: " << d.val() << std::endl;
    return 0;
}

由此产生的输出是:

Create Base
Copy Base
Create Derived from base
Value: 1

所以一个Base 创建,一个Base 的副本创建DerivedDerived 本身。

namedConstructor exit 或 nC2 没有副本,main 中的分配也没有。

我已经用-O0 编译了这个,但是那些这么小的函数可能在这里有优化,那些被删除了。

【讨论】:

  • 那份副本似乎没有必要吗?我不能假设派生类和基类在内存中会出现相同的情况吗?
  • 也许我可以换一种说法:在 reinterpret_cast 方向上我可以做些什么聪明的事情仍然满足 4?
  • 除非它是指针或引用,否则您无法将基类正确解释为派生类。那里的副本是因为您将基础对象转换为派生对象。您无法使用您要求的方法来避免这种情况。副本位于Derived (const Base &amp; b) : Base(b) { 副本位于Base(b),您无法避免将Base 转换为Derived 使用 而不是指针或引用。跨度>
猜你喜欢
  • 2012-11-13
  • 2018-02-02
  • 2012-12-12
  • 1970-01-01
  • 2017-08-25
  • 2017-05-12
  • 2013-10-16
  • 1970-01-01
相关资源
最近更新 更多