我有一段简单的代码(命令模式(,它不能按预期工作。
通常,多态类型在由指针或引用操作时会起作用。因此,如果Command
是一个接口,m_command
是一个参考,那么SetCommand
应该适用于不同具体类型的Command
(例如,CommandLightOn
和CommandGarageDoorOpen
(?事实并非如此。一旦用CommandLightOn
实例化了SimpleRemote
,则SetCommand
完全没有效果。在本例中,尝试将基础对象更改为CommandGarageDoorOpen
没有错误,但也没有效果。
如果SimpleRemote
中的m_command
更改为指针类型,则工作正常。所以问题是为什么引用类型在这种情况下不能工作?
class SimpleRemote
{
public:
SimpleRemote( Command& command ) : m_command{ command } {}
void SetCommand( Command& command )
{
m_command = command; //<-- broken
}
void ButtonPressed()
{
m_command.execute();
}
private:
Command& m_command;
};
输出:
光在上
灯亮#<--期望它打印";车库门打开了";
此示例的完整代码(简单命令模式(:
#include <iostream>
using std::cout;
class Light
{
public:
void On()
{
cout << "Light is onn";
}
private:
};
class GarageDoor
{
public:
void Up()
{
cout << "Garage door is upn";
}
private:
};
// the Command interface
class Command
{
public:
virtual void execute() = 0;
};
class CommandLightOn : public Command
{
public:
CommandLightOn( Light light ) : m_light{ light }{}
void execute() override
{
m_light.On();
}
private:
Light m_light;
};
class CommandGarageDoorOpen : public Command
{
public:
CommandGarageDoorOpen( GarageDoor door ) : m_door{door} {}
void execute() override
{
m_door.Up();
}
private:
GarageDoor m_door;
};
class SimpleRemote
{
public:
SimpleRemote( Command& command ) : m_command{ command } {}
void SetCommand( Command& command )
{
m_command = command;
}
void ButtonPressed()
{
m_command.execute();
}
private:
Command& m_command;
};
int main()
{
Light light;
CommandLightOn light_on( light );
SimpleRemote remote( light_on );
remote.ButtonPressed();
GarageDoor door;
CommandGarageDoorOpen door_open( door );
remote.SetCommand( door_open );
remote.ButtonPressed();
}
这里有两个问题。C++中的第一个引用不像指针那样是可重绑定的。这意味着像这样的代码:
int a = 1;
int& ref = a; // ref refers to a
int b = 2;
ref = b; // ref STILL referes to a
剩下a == 2
和b == 2
。这就是它的工作原理。
第二个问题就是所谓的对象切片。如果你仔细想想,这是唯一合乎逻辑的,因为引用与polyprphism代码一起工作,像这样的代码是合法的:
class A{
public:
virtual void foo() = 0;
};
class B : public A {
public:
void foo() override {}
};
class C : public A {
public:
void foo() override {}
};
int main() {
auto c = C{};
A& a = c;
auto b = B{};
a = b;
}
这是怎么回事?根据编译器默认生成的A::operator=
,对象c
的A
部分将替换为对象B的A
部分。您可以在A::operator=
中输入一些输出,以确认我在这里所说的内容。
把这两件事放在一起,你就有问题了。如何避免?首先,如果要重新绑定引用,请使用std::reference_wrappper
或纯旧指针。第二,考虑为接口/抽象类声明operator=()
已删除。这将使编译器在尝试错误地对对象进行切片时产生错误。
建议修复后的一些代码:
class Command
{
public:
Command& operator=(const Command&) = delete;
virtual void execute() = 0;
virtual ~Command() = default; // <-- this is good practice
};
////............... rest of code here
class SimpleRemote
{
public:
SimpleRemote(Command& command) : m_command{ command } {}
void SetCommand(Command& command)
{
m_command = command;
}
void ButtonPressed()
{
m_command.get().execute();
}
private:
std::reference_wrapper<Command> m_command;
};
引用必须初始化,但不能更新。不幸的是,编译器不会警告您!你可以在这里看到解释。
您必须使用指针而不是引用:
class SimpleRemote
{
public:
SimpleRemote( Command& command ) : m_command{ &command } {}
void SetCommand( Command& command )
{
m_command = &command;
}
void ButtonPressed()
{
m_command->execute();
}
private:
Command* m_command;
};