MSVC(2019 Community Preview)におけるC++のnew/delete
演算子の覚え書きです。
目次
- いつものnew/delete/new[]/delete[]演算子の実体はグローバルスコープの関数
- クラスの演算子オーバーロードによるクラススコープのnew/delete/new[]/delete[]の定義とグローバルスコープ版の呼び出し方法
- クラススコープのnew/delete/new[]/delete[]演算子の例
- new(std::nothrow)とplacement new
- 参考
なお、new/delete
よりも標準ライブラリ等のスマートポインタやコンテナの使用が推奨されます。
いつものnew/delete/new[]/delete[]
演算子の実体はグローバルスコープの関数
いつも使っているnew/delete/new[]/delete[]
演算子はグローバルスコープで定義された関数です(クラススコープも一部含まれるかもしれません)。Microsoft Visual Studio 2019 Community Previewではvcruntime_new.h
ヘッダーファイルに定義されていました。
operator new/delete/new[]/delete[]
関数は次の方法で呼び出せ、右クリックして定義を確認すれば上のヘッダーファイルを開けます。operator new/new[]
関数はvoid*
型を返すため、使用時はキャストする必要があります(未確認:コンストラクタ呼び出し)。
int main() { // new/delete void* p = ::operator new(5); ::operator delete(p); // new[]/delete[] void* a = ::operator new[](5); ::operator delete[](a); return 0; }
クラスの演算子オーバーロードによるクラススコープのnew/delete/new[]/delete[]
の定義とグローバルスコープ版の呼び出し方法
クラス(struct
またはclass
)でnew/delete/new[]/delete[]
演算子をオーバーロードすることで特別な動作を実装できます。オーバーロード時も::
名前空間(::new
など)を指定すれば強制的にグローバルスコープ版を呼び出せます。
#include <iostream> struct test1 { [[nodiscard]] void* operator new(size_t size) noexcept(false) { std::cout << "class scope operator new (size t) noexcept(false)" << std::endl; return ::operator new(size); } void operator delete(void* p) noexcept { std::cout << "class scope operator delete (void*)" << std::endl; return ::operator delete(p); } // 注意:new[]/delete[]はそれぞれオーバーロードが必要。 }; int main() { // クラススコープのnew/deleteを呼び出す。 test1* p1 = new test1; delete p1; // > class scope operator new (size t) noexcept(false) // > class scope operator delete (void*) // グローバルスコープのnew/deleteを呼び出す。 test1* p2 = ::new test1; ::delete p2; // (何も出力されない) return 0; }
クラススコープのnew/delete/new[]/delete[]
演算子の例
クラススコープのnew/delete/new[]/delete[]
演算子の例です。size_t size
引数を持つdelete[]
は関数形式でしか呼び出せないことに注意してください。
#include <iostream> struct test1 { // new/delete/new[]/delete[]演算子のsize引数がバイト数であることの確認用 double dummy; [[nodiscard]] void* operator new(size_t size) noexcept(false) { std::cout << "test1::operator new(size_t size) size=" << size << std::endl; return ::operator new(size); } void operator delete(void* p) noexcept { std::cout << "test1::operator delete(void* p)" << std::endl; ::operator delete(p); } [[nodiscard]] void* operator new[](size_t size) noexcept(false) { std::cout << "test1::operator new[](size_t size) size=" << size << std::endl; return ::operator new[](size); } void operator delete[](void* p) noexcept { std::cout << "test1::operator delete[](void* p)" << std::endl; ::operator delete[](p); } // 通常(delete[] ...時)は呼び出されない。必要なら関数形式で呼び出す。 void operator delete[](void* p, size_t size) noexcept { std::cout << "test1::operator delete[](void* p, size_t size) size=" << size << std::endl; ::operator delete[](p, size); } }; int main() { // class-scope operator new/delete(演算子形式) auto p1 = new test1; // test1* delete p1; // test1::operator new(size_t size) size=8 // test1::operator delete(void* p) // class-scope operator new/delete(関数形式) // 結果がvoid*となるので通常のdelete演算子は既定のdelete呼び出しとなりバグが混入する。 auto p2 = test1::operator new(sizeof(test1)); // void* test1::operator delete(p2); // test1::operator new(size_t size) size=8 // test1::operator delete(void* p) // class-scope operator new[]/delete[](size引数なし、演算子形式) auto p3 = new test1[5]; // test1* delete[] p3; // test1::operator new[](size_t size) size=40 // test1::operator delete[](void* p) // class-scope operator new[]/delete[](size引数なし、関数形式) // 結果がvoid*となるので通常のdelete演算子は既定のdelete呼び出しとなりバグが混入する。 auto p4 = test1::operator new[](sizeof(test1) * 5); // void* test1::operator delete[](p4); // test1::operator new[](size_t size) size=40 // test1::operator delete[](void* p) // class-scope operator new[]/delete[](size引数あり、演算子形式) // operator delete[](void*, size_t)は演算子形式で呼び出せない。 auto p5 = test1::operator new(sizeof(test1) * 5); // void* test1::operator delete[](p5, sizeof(test1) * 5); // test1::operator new(size_t size) size=40 // test1::operator delete[](void* p, size_t size) size=40*/ return 0; }
new(std::nothrow)とplacement new
通常のnew
演算子はメモリ確保失敗時に例外を発生しますが、new(std::nothrow) <type>
形式で呼び出せば例外時にnullptr
を返すようになります。その実体はconst std::nothrow_t&
型の追加引数を持つoperator new
関数です。
このような追加引数を持つnew
はplacement newと呼ばれます(元は配置先placementのアドレスを指定していたそうです)。基本的なplacement newは通常のnew
演算子(usual new)同様にグローバルスコープで定義されていますが、クラススコープでも実装できます。
次のソースコードのtest1
はクラススコープのnew
(usual new)、new(std::nothrow)
(例外を発生しないplacement new)、new(double)
(double
型を指定して呼び出されるplacement new)を実装します。
#include <iostream> #include <new> // std::nothrow_t struct test1 { [[nodiscard]] void* operator new(size_t size) noexcept(false) { std::cout << "test1::operator new(size_t size) noexcept(false)" << std::endl; return ::operator new(size); } [[nodiscard]] void* operator new(size_t size, const std::nothrow_t&) noexcept { std::cout << "test1::operator new(size_t size, const std::nothrow_t&) noexcept" << std::endl; return ::operator new(size); } [[nodiscard]] void* operator new(size_t size, double) noexcept(false) { std::cout << "test1::operator new(size_t size, double) noexcept(false)" << std::endl; return ::operator new(size); } void operator delete(void* p) noexcept { std::cout << "test1::operator delete(void* p) noexcept" << std::endl; ::operator delete(p); } }; int main() { auto p1 = new test1; auto p2 = new(std::nothrow) test1; auto p3 = new(0.15) test1; delete p1; delete p2; delete p3; // 出力 // test1::operator new(size_t size) noexcept(false) // test1::operator new(size_t size, const std::nothrow_t&) noexcept // test1::operator new(size_t size, double) noexcept(false) // test1::operator delete(void* p) noexcept // test1::operator delete(void* p) noexcept // test1::operator delete(void* p) noexcept return 0; }