編集:完全に言い直された質問:以下の回答:
派生型を基本型への参照として渡すときに問題が発生します。参照されたオブジェクトは、Derived から Base にキャストされる前後にデータがクリア/再初期化されているようです。
パラメータを Base オブジェクトへの参照として受け入れる関数に Derived インスタンスを渡すと (Derived -> Base&)、参照された Base インスタンスは、値として渡されない限り、Derived オブジェクトに対してメモリ内で一貫性を保つと考えました。
「読み取り専用」と見なされるデータの状態である Base クラスがあります。
class ReadonlyText
{
protected:
int m_Length;
char* m_String;
public:
ReadonlyText() m_Length(0), m_String(0) {} // a null base
ReadonlyText(const char* str) : m_Length(0), m_String(str)
{
do
{
m_Length++;
}while(*ptr++);
}
ReadonlyText(const ReadonlyText& copy) : m_Length(copy.m_Length), m_String(copy.m_String)
{
}
virtual ~Readonly() {};
... virtual nonsense
}; // lets not forget the semicolon
これで、書き込み可能な Derived クラスができました。
class Text
{
public:
Text() : m_Length(1), m_String(Memory::New<char>(m_Length)) {}
Text(const ReadonlyText& copy) : m_Length(copy.m_Length), m_String(Memory::New<char>(m_Length))
{
for(whole i = 0; i < m_Length; ++i)
m_String[i] = copy.m_String[i];
}
Text(const Text& copy) : m_Length(copy.m_Length), m_String(Memory::New<char>(m_Length))
{
for(whole i = 0; i < m_Length; ++i)
m_String[i] = copy.m_String[i];
}
virtual ~Text()
{
if((m_Length != 0 && m_String != null<char>::value))
{
Memory::Delete<Char>(m_String);
m_Length = 0;
m_String = null<Char>::value;
}
}
// operator==(ReadonlyText&)
// operator!=(ReadonlyText&)
// operator==(Text&)
// operator!=(Text&)
// operator=(Text&)
// operator=(ReadonlyText&)
// I don't have time to format all these functions for best visibility in SO,
// I assure you that all these functions are implemented properly
// THE PROBLEM occurs in any function with ReadonlyText&, say:
Text operator+(const ReadonlyText& rhs)
{
//...
// Before the function begins, 'rhs.m_String' == 0x0
// see case below
}
};
// ...
int main(...)
{
Text t1 = Text("hello");
Text t2 = Text("world");
// in the debugger, at this point, both t1 && t2 '.m_String' is valid
// as "hello" and "world" respectively
// but when stepping into:
Text t3 = t1 + t2; // the function described above...
// ...which is t1.operator+(t2) // where t2 is passed as ReadonlyText&
// the debugger shows:
// t1.m_String == "hello"
// t2.m_String == 0x0 -- this should be "world"
// since no copy construction is occuring, (passing as reference), then
// the data should be consistent with the derived type, right?
}
operator+ で、「rhs.m_String」にアクセスしようとしましたが、これは「world」に評価されますが、何らかの理由で、参照として渡されるのではなく、新しい ReadonlyText としてインスタンス化されていますか??
さらに、プログラムはメッセージでクラッシュしません。セグメンテーション違反やエラーはありません。代わりに、メインの終わりまでアプリケーションがスムーズに実行されたかのように、完全に終了しますか?
ここで説明する以上のことはありません。Memory::New と Memory::Delete はメモリ操作 (new と delete) をカプセル化し、New がテンプレートの型名のサイズと指定された長さに基づいてポインターを返すようにします。削除は、ポインターの適切な破棄を保証するだけです。null は、型名の一貫した null 値を保証する構造です。
//class Memory:
template<typename T> static T* New(whole length = 1)
{
return (T*)(operator new (length * sizeof(T)));
}
template<typename T> static void Delete(T* pointer)
{
operator delete (pointer);
}
// null struct
template<typename T>
struct null
{
static constexpr T* value = 0x0;
}
本当の問題:
<_< クラス階層を再設計するときに、派生メンバー (m_Length と m_String) を削除するのを忘れていました。したがって、Derived インスタンスには、Base メンバーをマスクするメンバーが含まれていました。簡単に言えば、何かが意味をなさない場合、それはおそらくあなたのせいです:
class Base
{
...
protected:
int m_Length;
char* m_String;
};
class Derived : public virtual Base
{
...
protected: // xD WTH was I hoping to do here???
int m_Length;
char* m_String;
};
マスキングを防ぐ継承用のコンパイラ機能があるはずですが、それまでは注意してください...ただ注意してください。私を助けようとした皆さん、そして手を引いた人たちに感謝します。