このスニペットを考えてみましょう:
_class Foo
{
int m_fileDescriptor;
public:
Bar transformIntoBar()
{
Bar bar(m_fileDescriptor);
m_fileDescriptor = -1;
return bar;
}
};
_
ご覧のとおり、Foo
がBar
に「変換」されると、その_m_fileDescriptor
_は_-1
_になり、オブジェクト全体が無効になります。
問題は、クラスのユーザーがそれが起こることを伝える方法がないことです。
1つの解決策は、フレンドmake_bar()
関数を宣言することです。
_friend Bar make_bar(Foo f);
_
そしてFoo
を移動のみにします(とにかく発生するはずですが、簡潔にするために例ではスキップしました)。このように、ユーザーは自分のFoo
オブジェクトが移動され、そのため役に立たなくなるという事実を認識する必要があります。
ただし、このソリューションに対しては、深刻な問題があります。このようなメソッド(たとえばmake_baz(Foo f)
など)が増えると、フレンドヘルパー関数のAPI全体を作成する必要がありますが、これは明らかに望ましいことではありません。
私の質問は-私のクラスのユーザーに大声で叫ぶために私ができることはそれを呼び出すと、オブジェクトが役に立たなくなるですか?
コメントでamonが述べたように、関数シグネチャに&&
を追加することで、thisを右辺値参照にすることができます。
Bar transformIntoBar() &&
{
...
}
このようにして、次のコードはコンパイルに失敗します。
Foo foo;
Bar bar = foo.transformIntoBar();
// compile error (clang):
// 'this' argument to member function 'transformIntoBar' is an lvalue, but function has rvalue ref-qualifier
ただし、以下はコンパイルされます。
Foo foo;
Bar bar = std::move(foo).transformIntoBar();
慣例により、そこから移動されたオブジェクトの多くは「特別な」状態にあるため、C++開発者はfoo
を使用しないか、ドキュメントを参照してどの状態であるかを知る必要があることを知っていますfoo
は移動後のものです。
これは、一時的な(名前のない)Foo
オブジェクトが既に右辺値であるため、作成される場合にも適切に処理します。
Bar bar = createFoo().transformIntoBar();
特定のケースに関する詳細情報で行うことができます。しかし、ビルダーオブジェクトと流暢なパターンを試すことをお勧めします
Bar b = Builder.CreateFoo() //ISettableFile
.SetFile() //IConvertable
.ConvertToBar() // Bar
Foo f = Builder.CreateFoo() //ISettableFile
.SetFile() //IConvertable
.ConvertToFoo() // Foo
インターフェイスとプライベートコンストラクターを使用して、基になるオブジェクトまたは変換関数へのアクセスを制限する
通常、オブジェクトのライフサイクルの制御を維持する必要がある場合は、オブジェクトをユーザーコードに直接渡さないほうがよいでしょう。これは、開閉ロジックを追加で必要とするI/Oに特に当てはまります。
つまり、オブジェクトを返す代わりに、オブジェクトを使用するロジックを提供する必要があります。もちろん、オブジェクトをロジックに提供しますが、オブジェクトには、管理する明確なライフサイクルがあります。
あなたがこれを示したクラスでは遅すぎると思います、あなたは1つ上のレベルの異なるデザインが必要になるでしょう。残念ながら、具体的な例を示すためのC++についてはほとんど知りませんが、コンセプト自体が役立つことを願っています。
C++のように見えるので、キャストは右辺値にのみ適用できると考えます。
class Foo {
...
operator Bar() && {
Bar r(m_fileDescriptor);
m_fileDescriptor = -1;
return r;
}
};