C++11 GCC 4 快速优化不在堆栈中存储来自抽象 class 的实现 class

C++11 GCC 4 Fast Optimization don't store implementation class from abstract class in the stack

伙计们,我有一个抽象 class,另一个 class 在堆栈中存储来自这个 class 的实现(我不想要堆分配,我也不想在不让调用者显式声明实现的情况下,我不知道还有其他方法可以做到这一点)和另一种存储此接口引用的方法 class。但是,GCC 似乎没有将实现 class 存储在堆栈中,并且当使用接口 class 时,可能找不到实现 class vtable。

基本上,在没有优化的情况下使用 GCC 4.8.1 编译时一切正常,但是当我尝试使用它时,程序崩溃然后 returns 139.

我不知道为什么 GCC 4 不支持它,而 GCC 5 支持,但我看到它们生成不同的指令。

编译器资源管理器:https://godbolt.org/z/Wfvj65

#include <cstdio>

#define FORCEINLINE inline __attribute__((always_inline))

class IFormatter
{
public:
    virtual void Format(const void* InData) const = 0;
};

template<typename T>
class TFormatter :
    public IFormatter
{
public:
    TFormatter() = delete;
};

using Scalar = float;

// Implemented, fine.
struct RVector2
{
    Scalar X;
    Scalar Y;
};

// Not implemented, get error.
struct RVector3
{
    Scalar X;
    Scalar Y;
    Scalar Z;
};

template<>
class TFormatter<RVector2> :
    public IFormatter
{
public:
    virtual void Format(const void*) const override
    {
        printf("[RVector2]\n");
    }
};

template<typename T>
class TCustom
{
public:
    FORCEINLINE TCustom(const T& InValue) :
        Value(InValue),
        Format(TFormatter<T>{})
    {
    }

    FORCEINLINE const T* Data() const
    {
        return &Value;
    }

    FORCEINLINE const IFormatter& Formatter() const
    {
        return Format;
    }

private:
    const T& Value;
    TFormatter<T> Format;
};

template<typename T>
FORCEINLINE TCustom<T> MakeCustom(const T& InValue)
{
    return TCustom<T>{ InValue };
}

class RCustom
{
public:
    FORCEINLINE RCustom(const void* InValue, const IFormatter& InFormatter) :
        Data(InValue),
        Formatter(InFormatter)
    {
    }

    template<typename T>
    FORCEINLINE RCustom(TCustom<T> const& InCustom) :
        RCustom(InCustom.Data(), InCustom.Formatter())
    {
    }

    FORCEINLINE const IFormatter& Get() const
    {
        return Formatter;
    }

private:
    const void* Data;
    const IFormatter& Formatter;
};

int main()
{
    const RVector2 Vector{};
    const RCustom Custom = MakeCustom(Vector);
    Custom.Get().Format(nullptr);
    
    return 0;
}

正如其中一条评论所说,将 TCustom 存储在不相关的类型 RCustom 中会发生一些奇怪的事情。隐式构造函数 RCustom(TCustom) 让我失望。

这个问题很微妙。如果某些东西适用于 -O0 但不适用于 -Ofast(或 -O2/-O3),大多数时候内存会发生一些有趣的事情。正如 所说,在您的情况下,问题是 RCustom 仅存储对 IFormatter:

的引用
class RCustom {
    ...
    const IFormatter& Formatter; // Asking for problems
}

这看似无辜&,实则危险。因为这个成员的有效性取决于外部对象的生命周期。有几种可能性可以解决这个问题。您可以在 RCustom 中保存 TFormatter 的副本(而不是引用):

template<typename T>
class RCustom {
    ...
    const TFormatter<T> Formatter;
}

但这也意味着您必须放弃抽象接口IFormatter,转而使用具体接口TFormatter<T>。要在 C++ 中使用虚方法,您需要一个指针,但使用原始指针会引入与引用相同的内存问题。所以我建议你使用智能指针:

class RCustom {
    ...
    std::shared_ptr<const IFormatter> Formatter;
}

PS:准确地说出了什么问题:在 MakeCustom() 中,您初始化了一个 TCustom 对象,该对象初始化并复制了 TFormatter 的一个实例。接下来,对 TCustomTFormatter 实例的引用保存在 RCustom 中。现在这个 RCustom 对象被返回并且函数 MakeCustom() 被清除。在这个清理过程中,TCustom 被销毁,TFormatter 成员也被销毁。但是 RCustom 仍然保留对这个无效内存的引用。在 C++ 中,& 和没有 & 之间的区别非常重要。