多态参考类型



我有一段简单的代码(命令模式(,它不能按预期工作。

通常,多态类型在由指针或引用操作时会起作用。因此,如果Command是一个接口,m_command是一个参考,那么SetCommand应该适用于不同具体类型的Command(例如,CommandLightOnCommandGarageDoorOpen(?事实并非如此。一旦用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 == 2b == 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=,对象cA部分将替换为对象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;
};

最新更新