So guys, I have an abstract class, other class that stores an implementation from this class in the stack (I don't want heap allocations and I don't know other way to do it without making the caller explicitly declares the implementation) and another that stores a reference of this interface class. But, it seems that GCC don't store the implementation class in the stack and when the interface class is used probably the implementation class vtable is not found.
Basically, everything works fine when compiled with GCC 4.8.1 without optimizations, but when I try to use it, the program crashes and then returns 139.
I don't know why GCC 4 doesn't support it, while GCC 5 does, but I see that they generate different instructions.
Compiler Explorer: 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;
}
As one of the comments said there is something weird going on with storing TCustom
in the unrelated type RCustom
. The implicit constructor RCustom(TCustom)
threw me off.
The issue is quite subtle. If something works with -O0 but not with -Ofast (or -O2/-O3), most of the time something funny is happening with the memory. As Benny K said, in your case the issue is that RCustom
only stores a reference to IFormatter
:
class RCustom {
...
const IFormatter& Formatter; // Asking for problems
}
This is seems like an innocent &
, but in fact this is dangerous. Because the validity of this member is dependent on the lifetime of an external object. There are a few possibilities to fix this. You could save a copy of the TFormatter
in RCustom
(instead of a reference):
template<typename T>
class RCustom {
...
const TFormatter<T> Formatter;
}
But this also means you have to give up the abstract interface IFormatter
for the concrete one TFormatter<T>
. To work with virtual methods in C++ you need a pointer, but using a raw-pointer will introduce the same memory problems as the references. So I suggest you use smart pointers:
class RCustom {
...
std::shared_ptr<const IFormatter> Formatter;
}
PS: to be precise about what's going wrong: In MakeCustom()
you initialize a TCustom
object which initializes and copies an instance of TFormatter
. Next a reference to the instance of TFormatter
in TCustom
is saved in RCustom
. Now this RCustom
object is returned and the function MakeCustom()
is cleaned up. In this cleaning process TCustom
is destroyed, and so is the TFormatter
-member. But the RCustom
still retains a reference to this invalid memory. In C++ the difference between an &
and no &
is rather important.