読者です 読者をやめる 読者になる 読者になる

ぷろみん

プログラミング的な内容を扱ってます

静的optional

モチベーション

ヘッダの依存性を減らしたい(コンパイラファイアウォールを強くしたい)
しかし、動的メモリ確保を行いたくない

おしいoptional

optionalは静的確保できるポインタの様な物です。
静的型をnullableにする為に使われます。

後でコンストラクトできるという特性から不完全型で構築できるかなと期待しますが、できません。
なぜなら静的なメモリを最初に用意する必要がある為です。
つまり、型のサイズさえ分かれば不完全型での宣言が可能になります。

実装

// optional.h
#pragma once
#include <cstring>

namespace torini{

// optionalの簡易イメージ
template<class T, int N>
class Optional{
public:
    void Construct(){
        T t;
        std::memcpy(this, &t, N);
    }
    
    T* operator ->(){
        return reinterpret_cast<T*>(this);
    }
private:
    // 通常はこれに加えて構築済みかどうかのフラグが存在する
    char storage[N];
};

}
// foo.h
#pragma once
#include <string>

namespace torini{

// 前方宣言用。なんでも良い
class Foo{
public:
    void Print() const;

private:
    std::string message_ = "hello world";
};

}
// foo.cpp
#include "foo.h"
#include <iostream>

namespace torini{

void Foo::Print() const{
        std::cout << message_ << std::endl;
}

}
// foo_size.h
#pragma once

namespace torini{

int GetFooSize();

}
// foo_size.cpp
#include "foo_size.h"
#include "foo.h"

namespace torini{

int GetFooSize(){
    // Fooのサイズを知る為にはfoo.hが必要
    return sizeof(Foo);
}

}
// main.cpp
#include "optional.h"
#include "foo_size.h"

namespace torini{

// torini::Fooを前方宣言だけする
class Foo;

}

int main(){
    // 前方宣言とサイズを取得する事により静的ストレージを確保しつつ好きなタイミングでコンストラクトできる
    torini::Optional<torini::Foo, torini::GetFooSize()> foo;

    // foo.hをincludeしないとコンストラクトはできない
    //foo.Construct();
    //foo->Print();
}

感想

面倒なので計測して問題時間が支配的になるまでは適当で良いと思います。

サイズ取得の為の関数がジェネリックじゃないのも良くないですし。