空クラスのサイズの問題
質問: メンバー変数やメンバー関数を使用せずに空の型を定義します。この型に対して sizeof を実行すると、結果はどうなりますか?
答え: 1
質問: なぜ 0 ではないのでしょうか?
回答: 空の型のインスタンスには情報が含まれていません。本来、sizeof は 0 であるべきですが、この型のインスタンスを宣言する場合、メモリ内で一定の領域を占有する必要があり、そうでない場合、これらのインスタンスは使用できません。どれだけのメモリが占有されるかについては、コンパイラによって決定されます。Visual Studio の空の型の各インスタンスは、1 バイトの領域を占有します。
質問: コンストラクターとデストラクターを型に追加し、その型に対して sizeof を実行すると、結果はどうなりますか?
回答: 以前と同じですが、依然として 1 です。コンストラクターとデストラクターを呼び出すために必要なのは、関数のアドレスだけです。これらの関数のアドレスは型にのみ関連しており、型のインスタンスとは何の関係もありません。コンパイラーは、関数に余分なものを追加しません。これら 2 つの機能により、インスタンスが作成されます。
質問: デストラクターが仮想関数としてマークされている場合はどうなりますか?
回答: C++ コンパイラは、型内の仮想関数を検出すると、その型の仮想関数テーブルを生成し、型の各インスタンス内の仮想関数テーブルへのポインタを追加します。32 ビット マシンでは、ポインターは 4 バイトの領域を占有するため、sizeof は 4 を返します。64 ビット マシンの場合、ポインターは 8 バイトの領域を占有するため、sizeof は 8 を返します。
代入演算子
質問: 以下は CMyString 型の宣言です。この型に代入演算子関数を追加してください。
class CMyString
{
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
};
チェックポイント:
1) 戻り値の型がその型の参照として宣言されており、関数終了前(つまり*this
)にインスタンスそのものの参照が返されているか。継続的な代入は、参照が返された場合にのみ許可されます。それ以外の場合、関数の戻り値が void の場合、この代入演算子を使用して連続代入を行うことはできません。str1、str2、str3 という 3 つの CMyString オブジェクトがあるとします。プログラム内のステートメント str1=str2=str3 はコンパイルされません。
2) 受信パラメータの型を定数参照として宣言するかどうか。渡されたパラメーターが参照ではなくインスタンスの場合、仮パラメーターから実パラメーターまでコピー コンストラクターが 1 回呼び出されます。パラメーターを参照として宣言すると、そのような不必要な消費を回避し、コードの効率を向上させることができます。同時に、代入演算子関数内では渡されたインスタンスの状態を変更しないため、渡された参照パラメータに const キーワードを追加する必要があります。
3) インスタンス自体の既存のメモリを解放するかどうか。新しいメモリを割り当てる前に自分自身の領域を解放するのを忘れると、プログラムはメモリ リークを起こします。
4) 受信パラメータと現在のインスタンス ( *this
) が同じインスタンスであるかどうかを判断するかどうか。同じである場合、代入操作は実行されず、直接返されます。事前に判断せずに割り当てを行うと、インスタンス自身のメモリが解放されるときに重大な問題が発生します。つまり、渡されたパラメータと*this
同じインスタンスの場合、自身のメモリが解放されると、渡されたパラメータのメモリが解放されます。も同時にリリースされたため、割り当てる必要のあるコンテンツが見つからなくなりました。
古典的なソリューション - ジュニア プログラマー
CMyString& CMyString::operator =(const CMyString &str)
{
if(this == &str)
return *this;
delete []m_pData;
m_pData = NULL;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
例外の安全性を考慮したソリューション - シニア プログラマー
前の関数では、メモリを割り当てる前に、delete を使用してインスタンス m_pData のメモリを解放しました。この時点でメモリが不足しており、new char が例外をスローすると、m_pData は null ポインタになり、プログラムが簡単にクラッシュする可能性があります。つまり、代入演算子関数内で例外がスローされると、CMyString のインスタンスは有効な状態を維持できなくなり、例外安全性の原則に違反します。
代入演算子関数に例外安全性を実装するには、2 つのアプローチがあります。簡単な方法は、new を使用して新しいコンテンツを割り当て、次に delete を使用して既存のコンテンツを解放することです。このようにして、元のコンテンツはコンテンツの割り当てが成功した後にのみ解放されるため、メモリ割り当てが失敗した場合でも CMyString のインスタンスが変更されないようにすることができます。より良い方法は、最初に一時インスタンスを作成してから、その一時インスタンスを元のインスタンスと交換することです。以下は、このアイデアのリファレンス コードです。
CMyString& CMyString::operator =(const CMyString &str)
{
if(this != &str)
{
CMyString strTemp(str);
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return *this;
}
この関数では、まず一時インスタンス strTemp を作成し、次に strTemp.m_pData をインスタンス自体の m_pData と交換します。strTemp はローカル変数ですが、プログラムが if の外で実行されると変数のスコープから外れ、strTemp のデストラクターが自動的に呼び出され、strTemp.mpData が指すメモリを解放します。strTemp.m_pData が指すメモリはインスタンス以前の m_pData のメモリであるため、これは自動的にデストラクタを呼び出してインスタンスのメモリを解放することに相当します。
新しいコードでは、new を使用して CMyString コンストラクターにメモリを割り当てます。メモリ不足により bad_alloc などの例外がスローされた場合、元のインスタンスの状態は変更されていないため、インスタンスの状態は依然として有効であり、例外の安全性が保証されます。
コード例
#include<cstring>
#include<cstdio>
class CMyString
{
public:
CMyString(const char* pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);
CMyString& operator = (const CMyString& str);
void Print();
private:
char* m_pData;
};
CMyString::CMyString(const char* pData)
{
if (pData == nullptr)
{
m_pData = new char[1];
m_pData[0] = '\0';
}
else
{
size_t length = strlen(pData);
m_pData = new char[length + 1];
strcpy(m_pData, pData);
}
}
CMyString::CMyString(const CMyString& str)
{
size_t length = strlen(str.m_pData);
m_pData = new char[length + 1];
strcpy(m_pData, str.m_pData);
}
CMyString::~CMyString()
{
delete[] m_pData;
}
/*
//初级用法
CMyString& CMyString::operator = (const CMyString& str)
{
if (this == &str)
return *this;
delete[]m_pData;
m_pData = nullptr;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
*/
//高级用法
CMyString& CMyString::operator =(const CMyString& str)
{
if (this != &str)
{
CMyString strTemp(str);
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return *this;
}
// ====================测试代码====================
void CMyString::Print()
{
printf("%s", m_pData);
}
void Test1()
{
printf("Test1 begins:\n");
const char* text = "Hello world";
CMyString str1(text);
CMyString str2;
str2 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str2.Print();
printf(".\n");
}
// 赋值给自己
void Test2()
{
printf("Test2 begins:\n");
const char* text = "Hello world";
CMyString str1(text);
str1 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str1.Print();
printf(".\n");
}
// 连续赋值
void Test3()
{
printf("Test3 begins:\n");
const char* text = "Hello world";
CMyString str1(text);
CMyString str2, str3;
str3 = str2 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str2.Print();
printf(".\n");
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str3.Print();
printf(".\n");
}
int main(int argc, char* argv[])
{
Test1();
Test2();
Test3();
return 0;
}