potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++ MSVC版new/delete演算子の覚え書き

MSVC(2019 Community Preview)におけるC++new/delete演算子の覚え書きです。

目次

なお、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;
}

参考