型強制をオーバーロードする際の注意事項
たとえば、クラス型からchar *型への強制を考えてみましょう。char*型への強制は演算子char *()のオーバーロードにすぎないことは誰もが知っていますが、私たちがしばしば気付かないのは、プログラミング後、予期しない現象が発生します(エラー例):
#include <iostream>
using namespace std;
#include <sstream>
#include <string>
class Person
{
private:
string name;
int age;
public:
Person() = default;
Person(string name, int age);
Person(Person& obj);
operator char*();
~Person() = default;
};
Person::Person(string name, int age)
{
this->name = name;
this->age = age;
}
Person::Person(Person & obj)
{
this->name = obj.name;
this->age = obj.age;
}
Person::operator char*()
{
ostringstream sstr;
sstr << this->name << "的年龄为" << this->age;
return const_cast<char*>(sstr.str().c_str());
}
int main()
{
Person Person_Obj1("张三", 19);
cout << Person_Obj1 << endl;
}
出力結果:(すべて文字化け)
理由を調べてみましょう:
この理由は、返されるのは一時メモリ空間へのchar *ポインタであるためです。つまり、char *タイプのポインタを返すときです(注:char *強制をオーバーロードすると、クラスタイプTheオブジェクトは、cout出力中に出力するために暗黙的にchar *変数に変換されます)cout出力の前に、次の例に示すように、char *型ポインターが指すメモリー空間が解放されています。
#include <iostream>
using namespace std;
#include <sstream>
#include <string>
char* ShowInf()
{
ostringstream sstr("张三");
return const_cast<char*>(sstr.str().c_str()); // 返回指向临时存储空间的char*型指针
}
int main()
{
cout << ShowInf() << endl; // 输出之前伴随着ShowInf()函数生命的结束里面的临时变量已经被销毁
}
出力結果:(文字化けしたコードを出力)
したがって、オーバーロードされたプログラムに次の改善を加えます。
クラスタイプに新しい変数を追加します。この変数のタイプは、オーバーロードするキャスト演算子の戻り値と一致しています。
char *型のキャスターをオーバーロードしたいので、クラス型にchar *変数を定義して、キャストの結果を格納します。
#include <iostream>
using namespace std;
#include <sstream>
#include <string>
class Person
{
private:
string name;
int age;
char* Output;
public:
Person() = default;
Person(string name, int age);
Person(Person& obj);
operator char*();
~Person() = default;
};
Person::Person(string name, int age)
{
this->name = name;
this->age = age;
}
Person::Person(Person & obj)
{
this->name = obj.name;
this->age = obj.age;
}
Person::operator char*()
{
ostringstream sstr;
sstr << this->name << "的年龄为" << this->age;
this->Output = new char[sstr.str().size()];
strcpy(this->Output, sstr.str().c_str());
return this->Output;
}
int main()
{
Person Person_Obj1("张三", 19);
cout << Person_Obj1 << endl;
}
出力結果:
出力結果は正常であることがわかります。「改善前」と「改善後」の演算子char *()のコードの違いを比較します。
改善前:
Person::operator char*()
{
ostringstream sstr;
sstr << this->name << "的年龄为" << this->age;
return const_cast<char*>(sstr.str().c_str()); // 返回指向临时存储区域的指针(临时变量其实就是在栈(stack)当中存储的变量,它会随着局部变量堆在函数的释放而被释放掉)
}
改善後:
Person::operator char*()
{
ostringstream sstr;
sstr << this->name << "的年龄为" << this->age;
this->Output = new char[sstr.str().size()]; // 动态申请内存空间,将数据存在堆区(heap)中
strcpy(this->Output, sstr.str().c_str()); // 将临时变量的值拷贝至堆区变量当中
return this->Output; // 输出堆区变量
}
したがって、将来、必須の変換演算子をオーバーロードして取得した対応する型のデータをこの型に格納し、一時変数を返さないようにする必要があります。一時変数を返す操作は実行できません。
再び改善されたバージョン:要求されたメモリスペースを解放します:
#include <iostream>
using namespace std;
#include <sstream>
#include <string>
class Person
{
private:
string name;
int age;
char* Output;
public:
Person() = default;
Person(string name, int age);
Person(Person& obj);
explicit operator char*();
void Cout();
~Person();
};
Person::~Person()
{
if (this->Output != nullptr) // 防止再次被释放引发错误
{
delete[] this->Output;
}
}
Person::Person(string name, int age)
{
this->name = name;
this->age = age;
}
Person::Person(Person & obj)
{
this->name = obj.name;
this->age = obj.age;
}
Person::operator char*()
{
ostringstream sstr;
sstr << this->name << "的年龄为" << this->age;
this->Output = new char[sstr.str().size() + 1]; // 申请内存空间的时候一定要多预留一个空间
strcpy(this->Output, sstr.str().c_str());
return this->Output;
}
void Person::Cout()
{
cout << static_cast<char*>(*this) << endl;
delete[] this->Output;
this->Output = nullptr;
}
int main()
{
Person Person_Obj1("张三", 19);
Person_Obj1.Cout();
}
コードでは、次の改善を行いました。
出力関数を作成しました:
void Person::Cout()
{
cout << static_cast<char*>(*this) << endl;
delete[] this->Output;
this->Output = nullptr;
}
この関数は出力をカスタマイズでき、動的に割り当てられたメモリスペースを再利用することもできます。
Person::~Person()
{
if (this->Output != nullptr) // 防止再次被释放引发错误
{
delete[] this->Output;
}
}
解放されたメモリが再び解放されないように、必ずこのコードをデストラクタに含めてください。
explicit operator char*();
オーバーロードされたchar * casterを明示的な型として宣言し、暗黙的な型変換を実行しないようにシステムに指示しました。この操作により、暗黙的な型変換中にメモリスペースを要求するthis-> Outputによって引き起こされるメモリリークを防ぐことができます。
操作中に、次の問題が発生しました。
CRTは、ヒープバッファの終了後にアプリケーションがメモリに書き込んだことを検出しました
この文が示しているのは、解放したメモリスペースがそれ自体の長さを超えていることです。つまり、char型配列の最後の要素は「\ 0」が原因ではなく、空きメモリに終わりはありません。スペースが原因でエラーが発生するため、次の改善を行いました。
Person::operator char*()
{
ostringstream sstr;
sstr << this->name << "的年龄为" << this->age;
this->Output = new char[sstr.str().size() + 1]; // 申请内存空间的时候一定要多预留一个空间
strcpy(this->Output, sstr.str().c_str());
return this->Output;
}
これのためにもう1つのアプリケーションスペースを予約しました->「\ 0」文字配列の終了マーカーを配置するための出力。改善の場合、明らかにこれを行いませんでした。私の改善コードは次のとおりです。
Person::operator char*()
{
ostringstream sstr;
sstr << this->name << "的年龄为" << this->age;
this->Output = new char[sstr.str().size()];
strcpy(this->Output, sstr.str().c_str());
return this->Output;
}
私たちは質問します:なぜこのコードには何もないのですか?
デストラクタがthis-> Outputが指すメモリ空間を解放しなかったので、なぜですか?
this-> Outputが指すメモリスペースを解放するためのデストラクタ〜Person()を定義しなかったため、システムのデフォルトデストラクタ "〜Person = default"を使用しました。したがって、このコードで動的に割り当てられたメモリスペースは次のように解放されます。プログラム全体が終了します。
また、プログラム全体をリリースするときに「オーバーリリース」エラーが発生しないのはなぜですか。
エラーの理由は、解放したメモリスペースがプログラム内の他のストレージユニットの利益に触れたため、プログラムはエラーを報告しますが、プログラム全体を解放すると、誰が誰の利益を侵害するかという問題はありません。とにかく、すべての利点(メモリ占有)はすべて解放されます。