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
的一个实例。接下来,对 TCustom
中 TFormatter
实例的引用保存在 RCustom
中。现在这个 RCustom
对象被返回并且函数 MakeCustom()
被清除。在这个清理过程中,TCustom
被销毁,TFormatter
成员也被销毁。但是 RCustom
仍然保留对这个无效内存的引用。在 C++ 中,&
和没有 &
之间的区别非常重要。
伙计们,我有一个抽象 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
的一个实例。接下来,对 TCustom
中 TFormatter
实例的引用保存在 RCustom
中。现在这个 RCustom
对象被返回并且函数 MakeCustom()
被清除。在这个清理过程中,TCustom
被销毁,TFormatter
成员也被销毁。但是 RCustom
仍然保留对这个无效内存的引用。在 C++ 中,&
和没有 &
之间的区别非常重要。