gpt4 book ai didi

c++ - 如何避免在这个特定的类层次结构设计中向下转型?

转载 作者:行者123 更新时间:2023-12-01 14:25:42 26 4
gpt4 key购买 nike

我的任务是创建一种多平台 C++ GUI 库。它在不同的平台上封装了不同的 GUI 框架。该库本身提供了一个界面,无论用户使用什么平台,用户都可以通过该界面进行统一通信。

我需要正确设计此接口(interface)以及与框架的底层通信。我试过的是:

  1. Pimpl 惯用语 - 最初选择此解决方案是因为它的优点 - 二进制兼容性、削减依赖树以增加构建时间...
class Base {
public:
virtual void show();
// other common methods
private:
class impl;
impl* pimpl_;
};

#ifdef Framework_A
class Base::impl : public FrameWorkABase{ /* underlying platform A code */ };
#elif Framework_B
class Base::impl : public FrameWorkBBase { /* underlying platform B code */ };
#endif

class Button : public Base {
public:
void click();
private:
class impl;
impl* pimpl_;
};

#ifdef Framework_A
class Button::impl : public FrameWorkAButton{ /* underlying platform A code */ };
#elif Framework_B
class Button::impl : public FrameWorkBButton { /* underlying platform B code */ };
#endif

但是,据我所知,这种模式并不是为如此复杂的层次结构而设计的,在这种层次结构中,您可以轻松地扩展接口(interface)对象及其实现。例如。如果用户想从库中继承按钮 UserButton : Button , 他需要了解 pimpl 习语模式的细节才能正确初始化实现。

  1. 简单的实现指针——用户不需要了解库的底层设计——如果他想创建一个自定义控件,他只需将库控件子类化,其余的由库处理
#ifdef Framework_A
using implptr = FrameWorkABase;
#elif Framework_B
using implptr = FrameWorkBBase;
#endif

class Base {
public:
void show();
protected:
implptr* pimpl_;
};

class Button : public Base {
public:
void click() {
#ifdef Framework_A
pimpl_->clickA(); // not working, need to downcast
#elif Framework_B
// works, but it's a sign of a bad design
(static_cast<FrameWorkBButton>(pimpl_))->clickB();
#endif
}
};

由于实现受到保护,相同的implptr对象将用于 Button - 这是可能的,因为 FrameWorkAButtonFrameWorkBButton继承自 FrameWorkABBaseFrameWorkABase分别。这个解决方案的问题是每次我需要调用在 Button类似pimpl_->click()的类, 我需要低调 pimpl_ ,因为 clickA()方法不在 FrameWorkABase 中但在 FrameWorkAButton , 所以它看起来像这样 (static_cast<FrameWorkAButton>(pimpl_))->click() .过度向下转型是糟糕设计的标志。在这种情况下,访问者模式是 Not Acceptable ,因为 Button 支持的所有方法都需要一个访问方法。类和一大堆其他类。

有人可以告诉我如何修改这些解决方案或者建议其他一些在这种情况下更有意义的解决方案吗?提前致谢。

编辑 基于 od @ruakh 的回答

所以 pimpl 解决方案看起来像这样:

class baseimpl; // forward declaration (can create this in some factory)
class Base {
public:
Base(baseimpl* bi) : pimpl_ { bi } {}
virtual void show();
// other common methods
private:
baseimpl* pimpl_;
};

#ifdef Framework_A
class baseimpl : public FrameWorkABase{ /* underlying platform A code */ };
#elif Framework_B
class baseimpl : public FrameWorkBBase { /* underlying platform B code */ };
#endif


class buttonimpl; // forward declaration (can create this in some factory)
class Button : public Base {
public:
Button(buttonimpl* bi) : Base(bi), // this won't work
pimpl_ { bi } {}
void click();
private:
buttonimpl* pimpl_;
};

#ifdef Framework_A
class Button::impl : public FrameWorkAButton{ /* underlying platform A code */ };
#elif Framework_B
class Button::impl : public FrameWorkBButton { /* underlying platform B code */ };
#endif

问题在于调用 Base(bi)Button里面的 ctor 将无法工作,因为 buttonimpl不继承baseimpl , 只有它的子类 FrameWorkABase .

最佳答案

The problem with this solution is that every time i need to call e.g. in Button class something like pimpl_->click(), I need to downcast the pimpl_, because clickA() method is not in FrameWorkABase but in FrameWorkAButton, so it would look like this (static_cast<FrameWorkAButton>(pimpl_))->click().

我可以想到三种方法来解决这个问题:

  1. 消除 Base::pimpl_ 以支持纯虚拟保护函数 Base::pimpl_()。让子类实现该函数以提供指向 Base::show 的实现指针(以及任何其他需要它的基类函数)。
  2. 将 Base::pimpl_ 设为私有(private)而非 protected ,并为子类提供它们自己的实现指针的适当类型拷贝。 (由于子类负责调用基类构造函数,因此它们可以确保为它提供与它们计划使用的相同的实现指针。)
  3. 使 Base::show 成为一个纯虚函数(以及任何其他基类函数),并在子类中实现它。如果这导致代码重复,请创建子类可以使用的单独辅助函数。

我认为#3 是最好的方法,因为它避免了将类层次结构与底层框架的类层次结构耦合;但我从你上面的评论中怀疑你会不同意。没关系。


E.g. if the user wanted to subclass button from the library UserButton : Button, he would need to know the specifics of the pimpl idiom pattern to properly initialize the implementation.

无论采用何种方法,如果您不希望客户端代码必须设置实现指针(因为这意味着与底层框架交互),那么您将需要提供这样做的构造函数或工厂方法。由于您希望通过客户端代码支持继承,这意味着提供处理此问题的构造函数。所以我认为您过快地取消了 Pimpl 习语。


关于您的编辑 — 而不是让 Base::impl 和 Button::impl 扩展 FrameworkABase 和 FrameworkAButton,您应该使 FrameworkAButton 成为 数据成员 Button::impl,并给 Base::impl 一个指向它的指针。 (或者您可以为 Button::impl 提供一个 std::unique_ptr 给 FrameworkAButton 而不是直接持有它;这使得以定义明确的方式将指针传递给 Base::impl 变得更容易一些。)

例如:

#include <memory>

//////////////////// HEADER ////////////////////

class Base {
public:
virtual ~Base() { }
protected:
class impl;
Base(std::unique_ptr<impl> &&);
private:
std::unique_ptr<impl> const pImpl;
};

class Button : public Base {
public:
Button(int);
virtual ~Button() { }
class impl;
private:
std::unique_ptr<impl> pImpl;
Button(std::unique_ptr<impl> &&);
};

/////////////////// FRAMEWORK //////////////////

class FrameworkABase {
public:
virtual ~FrameworkABase() { }
};

class FrameworkAButton : public FrameworkABase {
public:
FrameworkAButton(int) {
// just a dummy constructor, to show how Button's constructor gets wired
// up to this one
}
};

///////////////////// IMPL /////////////////////

class Base::impl {
public:
// non-owning pointer, because a subclass impl (e.g. Button::impl) holds an
// owning pointer:
FrameworkABase * const pFrameworkImpl;

impl(FrameworkABase * const pFrameworkImpl)
: pFrameworkImpl(pFrameworkImpl) { }
};

Base::Base(std::unique_ptr<Base::impl> && pImpl)
: pImpl(std::move(pImpl)) { }

class Button::impl {
public:
std::unique_ptr<FrameworkAButton> const pFrameworkImpl;

impl(std::unique_ptr<FrameworkAButton> && pFrameworkImpl)
: pFrameworkImpl(std::move(pFrameworkImpl)) { }
};

static std::unique_ptr<FrameworkAButton> makeFrameworkAButton(int const arg) {
return std::make_unique<FrameworkAButton>(arg);
}

Button::Button(std::unique_ptr<Button::impl> && pImpl)
: Base(std::make_unique<Base::impl>(pImpl->pFrameworkImpl.get())),
pImpl(std::move(pImpl)) { }
Button::Button(int const arg)
: Button(std::make_unique<Button::impl>(makeFrameworkAButton(arg))) { }

///////////////////// MAIN /////////////////////

int main() {
Button myButton(3);
return 0;
}

关于c++ - 如何避免在这个特定的类层次结构设计中向下转型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60752964/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com