10

我对 C++ 相当陌生,这就是我遇到的问题:我有两个类,Client并且Host. 当所有内容都加载完毕后,您可以选择按下两个按钮,如果按下按钮 1Client则加载,如果按下按钮 2Host则加载。

现在两者Client都是Host相当大的类,我不想把它们都放在记忆中。所以我的想法是创建一个Base类,然后两者都Client应该Host扩展基类,然后我唯一要做的就是:

Base connection;

//If button 1 is pressed:
connection = Client();

//If button 2 is pressed:
connection = Host();

好吧,这听起来好得令人难以置信,当我尝试它时,我没有收到任何错误。现在问题来了,Base有一个名为 的函数AClient还有一个名为 的函数B。所以这个函数B是类独有的Client

当我尝试调用函数时,B我收到此错误:'class Base' has no member named 'B'. 我怎样才能让 C++ 知道我正在与班级交谈ClientHost代替Base?我也愿意采用全新的方法来解决这个问题。也许这只是我思维过程中的一个错误。

提前致谢!

4

4 回答 4

6

您遇到了我们称之为对象切片的情况,这是具有值语义的语言(如 C++)中的常见问题。

当您将子类型的值分配给connection超类型的新位置(您的变量)时,就会发生对象切片。这引入了实例的新副本,但它是超类型,而不是子类型,因此您丢失了有关您想要实例化的具体类的信息。

为避免这种情况,您有多种选择。

经典方法使用指针

Base * connection;
connection = new YourConcreteType();

然后,要使用此对象,您必须使用星号运算符 ( ) 取消对它的引用*

(*connection).function();
connection->function();    // syntactic sugar, semantically equivalent

不要忘记:您必须在使用后删除对象:

delete connection;

为了简化这一点,C++ 引入了两个概念:引用智能指针。虽然前者有一个限制,只能分配一次,但它是语法上最简单的一个。后者类似于指针方法,但您不必关心删除,因此您不太可能遇到内存泄漏情况:

std::shared_ptr<Base> connection;

connection = make_shared<YourConcreteType>(); // construction via 'make_shared'

// ... use as if it were just a pointer ...

connection->function();

// no delete!

还有其他“智能指针”类型,例如unique_ptr,如果您不打算传递指针(如果它保持在范围内),可以使用它们。

现在,您可以分别实现两个类中的功能。为了利用多态性,这意味着在运行时,要么调用一个子类的函数,要么调用另一个子类的函数,这取决于构造了哪个,你应该将基类中的函数声明为 is virtual,否则,函数Base无论您构造的具体类型如何,都会调用定义。

在您的情况下,您想要调用一个应该根据类型执行不同操作的函数。虽然您的方法是引入两个不同的函数,即Aand B,但您可以只声明一个函数,让我们将其handleEvent称为基类中的纯虚拟(=抽象)函数,这意味着“此函数将在子类中实现classes”,并在两个子类中独立定义:

Base {
    ....
    virtual void handleEvent(...) = 0; // "= 0" means "pure"
};

// Don't provide an implementation

Client {
    void handleEvent(...); // override it
};

// define it for Client:
void Client::handleEvent(...) {
    ...
}

Host {
    void handleEvent(...); // override it
};

// define it for Host:
void Host::handleEvent(...) {
    ...
}
于 2013-01-23T23:46:26.300 回答
3

当你这样做时:

Base connection;

您创建的对象connection恰好具有 type 所需的内存Base

当你这样做时:

connection = Client();

您没有扩展分配的内存或转换connectionClient实例。相反,您正在创建一个更大的对象,该对象被“切片”成一个较小的对象。

您想要做的是使用指针(或引用或智能指针),以便您保留的只是可能是一种类型或另一种类型的对象的地址。像这样:

Base *connection;
...
connection = new Client();

第一条语句创建一个指向Base类型化对象的指针,第二条语句为类型化对象分配内存,对其进行Client初始化并将其地址分配给connection.

于 2013-01-23T23:46:08.533 回答
2

如果您仍然想使用 Base ,那么您将不得不这样做才能使用这些功能:

if (Button1) {
    dynamic_cast<Client*>(connection)->A();
} else {
    dynamic_cast<Host*>(connection)->B();   
}  

您需要将连接设为指针。Base * connection.

不过,这并不理想。您应该像其他答案一样研究一种不同的方法。

于 2013-01-23T23:47:55.450 回答
2

首先,这条线

connection = Client();

正在使用赋值运算符从临时对象设置connectiona的状态。仍然是一个对象。您可以执行以下操作:BaseClientconnectionBase

std::unique_ptr<Base> makeBase(someFlagType flag)
{
  if (flag) {
    return std::unique_ptr<Base>(new Cient);
  } else {
    return std::unique_ptr<Base>(new Host);
  }
}

然后

std::unique_ptr<Base> b = makeBase(myFlag);
b->someBaseMethod();

关于铸造部分,我想说,如果你发现自己不得不强制转换为子类型,你应该重新考虑类的设计。

于 2013-01-23T23:49:15.817 回答