注:これは、私がしばらく前に投稿した質問の完全な言い回しです。重複している場合は、もう一方を閉じてください。
私の問題は非常に一般的ですが、具体的な簡単な例に基づいて、より簡単に説明できるようです。オフィスの電力消費量をシミュレートしたいとします。照明と暖房しかないと仮定しましょう。
class Simulation {
public:
Simulation(Time const& t, double lightMaxPower, double heatingMaxPower)
: time(t)
, light(&time,lightMaxPower)
, heating(&time,heatingMaxPower) {}
private:
Time time; // Note : stack-allocated
Light light;
Heating heating;
};
class Light {
public:
Light(Time const* time, double lightMaxPower)
: timePtr(time)
, lightMaxPower(lightMaxPower) {}
bool isOn() const {
if (timePtr->isNight()) {
return true;
} else {
return false;
}
}
double power() const {
if (isOn()) {
return lightMaxPower;
} else {
return 0.;
}
private:
Time const* timePtr; // Note : non-owning pointer
double lightMaxPower;
};
// Same kind of stuff for Heating
重要な点は次のとおりです。
1.Time
データ メンバーに移動できない、Light
またはHeating
その変更がこれらのクラスのいずれにも由来しないため。
2.Time
にパラメーターとして明示的に渡す必要はありませんLight
。実際、Light
プログラムのどの部分にも、パラメーターとして提供したくない への参照が存在する可能性がTime
あります。
class SimulationBuilder {
public:
Simulation build() {
Time time("2015/01/01-12:34:56");
double lightMaxPower = 42.;
double heatingMaxPower = 43.;
return Simulation(time,lightMaxPower,heatingMaxPower);
}
};
int main() {
SimulationBuilder builder;
auto simulation = builder.build();
WeaklyRelatedPartOfTheProgram lightConsumptionReport;
lightConsumptionReport.editReport((simulation.getLight())); // No need to supply Time information
return 0;
}
これで、Simulation
コピー/移動が構築されていない限り、完全に見つかります。そうである場合は、コピー/移動も構築され、デフォルトで、Time へのポインターはコピー/移動元の古いインスタンスをLight
指しているからです。ただし、実際には、return ステートメントとオブジェクト作成の間で copy/move が構築されます。Time
Simulation
Simulation
SimulationBuilder::build()
main()
現在、問題を解決する方法はいくつかあります。
1: コピー省略に依存します。この場合(そして私の実際のコードでは)、コピーの省略は標準で許可されているようです。しかし必須ではなく、実際のところ、clang -O3 によって省略されることはありません。より正確に言うと、clang はSimulation
コピーを省略しますが、move ctor for を呼び出しますLight
。また、実装依存の時間に依存することは堅牢ではないことに注意してください。
2: で move-ctor を定義しSimulation
ます。
Simulation::Simulation(Simulation&& old)
: time(old.time)
, light(old.light)
, heating(old.heating)
{
light.resetTimePtr(&time);
heating.resetTimePtr(&time);
}
Light::resetTimePtr(Time const* t) {
timePtr = t;
}
これは機能しますが、ここでの大きな問題はカプセル化を弱めることです:移動中により多くの情報が必要でSimulation
あることを知る必要があります。Light
この単純化された例では、これはそれほど悪くはありませんが、timePtr
直接ではなくLight
、そのサブサブサブメンバーの 1 つにあると想像してください。それから私は書かなければならないでしょう
Simulation::Simulation(Simulation&& old)
: time(old.time)
, subStruct(old.subStruct)
{
subStruct.getSubMember().getSubMember().getSubMember().resetTimePtr(&time);
}
これは、カプセル化とデメテルの法則を完全に破ります。関数を委任するときでさえ、私はそれが恐ろしいと思います。
Time
3: によって監視されているある種のオブザーバー パターンを使用し、メッセージを受信するときにポインターを変更するLight
ように、コピー/移動が構築されているときにメッセージを送信します。Light
私はそれの完全な例を書くのが面倒だと告白しなければなりません。
4: で所有ポインタを使用しますSimulation
。
class Simulation {
private:
std::unique_ptr<Time> const time; // Note : heap-allocated
};
が移動Simulation
すると、Time
メモリは ではないため、 のポインタLight
は無効になりません。実際、これは他のほとんどすべてのオブジェクト指向言語が行うことです。すべてのオブジェクトはヒープ上に作成されるからです。今のところ、私はこの解決策を支持していますが、まだ完全ではないと考えています。B. Stroustrup が、必要のないときはポインタを使用すべきではなく、必要な場合は多かれ少なかれポリモーフィックであることを意味すると言っているのを聞いたことがあります。
5:Simulation
返されることなく、その場で構築しSimulationBuilder
ます (その後、コピー/移動 ctor/代入をSimulation
すべて削除できます)。例えば
class Simulation {
public:
Simulation(SimulationBuilder const& builder) {
builder.build(*this);
}
private:
Time time; // Note : stack-allocated
Light light;
Heating heating;
...
};
class SimulationBuilder {
public:
void build(Simulation& simulation) {
simulation.time("2015/01/01-12:34:56");
simulation.lightMaxPower = 42.;
simulation.heatingMaxPower = 43.;
}
};
今、私の質問は次のとおりです。
1: どのようなソリューションを使用しますか? もう一つ考えてみませんか?
2: 元のデザインに何か問題があると思いますか? それを修正するためにあなたは何をしますか?
3: このようなパターンに遭遇したことがありますか? 私のコード全体でかなり一般的です。Time
ただし、実際にはポリモーフィックであり、ヒープが割り当てられているため、一般的にはこれは問題ではありません。
4: 問題の根本に戻ると、「移動する必要はありません。移動できないオブジェクトをその場で作成したいだけですが、コンパイラはそうすることができません」なぜないのですか? C ++での簡単な解決策と、別の言語での解決策はありますか?