C++ 流畅接口

3

我正在为我的C++ GUI库实现一个流畅接口。基本概念是每个函数都返回一个模板的指针。

这里有两个类。

template <class T>
class wnd {
public:
    T* set_id(int id) { id_ = id; return (T*)this; }
    T* set_text(std::string text) { text_ = text; return (T*)this; }
protected:
    int id_;
    std::string text_;
};
    
class app_wnd : public wnd<app_wnd> {
public:
    app_wnd* show() { std::cout << text_ << " " << id_; return (app_wnd*) this; }
};

这很好用。当使用app_wnd时,子函数返回正确的T*,即app_wnd*。因此我可以执行(new app_wnd())->set_id(0)->set_text("hello world")->show();

当我进行另一个派生时,问题就出现了。

class main_wnd : public app_wnd {
public:
    main_wnd* set_app_instance(int instance) { instance_ = instance; return (main_wnd *) this; }
protected : 
    int instance_;
};

这个类不知道底层模板,一旦我调用set_id()函数,它会返回app_wnd类,失去了main_wnd的方法,即(new main_wnd())->set_id(0)->set_app_instance(10);将失败,因为set_id函数将返回app_wnd*而不是main_wnd*。

有没有一个干净、好的、非复杂的解决方案?


2
不,你不能使用new app_wnd(),因为app_wnd是一个模板。由于示例无法正常工作,所以您现在遇到的问题不仅仅是这个(而且class app_wnd : public wnd<app_wnd>也无法编译)。 - Alberto Sinigaglia
抱歉,我复制粘贴了旧版本的代码。我现在已经更新了它。问题仍然存在。 - Tomaz Stih
CRTP需要一直使用模板,因为每个人都必须能够转换回顶层类。template<typename T> class app_wnd : public wnd<T> { ... }; class main_wnd : public app_wnd<main_wnd> { ... }; - Raymond Chen
@Berto99 class app_wnd : public wnd<app_wnd> 为什么不行呢?app_wnd并不完整,但也没有必要。 - 463035818_is_not_a_number
@idclev463035818 你正在观看一个不同的代码,我之前在关注的那个问题已经被编辑了。 - Alberto Sinigaglia
我理解得对吗,CRTP 要求知道派生链中哪个类是“最终类”?因此,如果我有一个按钮并想从它创建 custom_button,则按钮不能是最终类,因为它需要通过模板传递。 - Tomaz Stih
2个回答

1

我现在感觉有点年轻了,因为我在Stack Exchange上找到了答案。所需的是使用默认模板参数的修改后的CRTP。以下是类代码。

template <typename Q, typename T>
struct fluent_type {
    typedef Q type;
};

template <typename T>
struct fluent_type<void, T> {
    typedef T type;
};

template <typename T>
class wnd {
public:
    typedef typename fluent_type<T, wnd<void>>::type ftype;
    ftype* set_id(int id) { id_ = id; return static_cast<ftype*>(this); }
    ftype* set_text(std::string text) { text_ = text; return static_cast<ftype*>(this); }
protected:
    int id_;
    std::string text_;
};

template <typename T =  void>
class app_wnd : public wnd<app_wnd<T>> {
public:
    typedef typename fluent_type<T, wnd<app_wnd<T> > >::type ftype;
    ftype* show() { std::cout << text_ << " " << id_; return static_cast<ftype*>(this);
    }
};

template <typename T = void>
class main_wnd : public app_wnd<main_wnd<T>> {
public:
    typedef typename fluent_type<T, app_wnd<main_wnd<T> > >::type ftype;
    ftype* set_app_instance(int instance) { instance_ = instance; return static_cast<ftype*>(this); }
protected : 
    int instance_;
};

这是示例调用。

auto aw = (new app_wnd<>())
    ->set_id(0)
    ->set_text("hello app_wnd")
    ->show();

auto mw = (new main_wnd<>())
    ->set_id(0)
    ->set_text("hello main_wnd")
    ->show()
    ->set_app_instance(123);

0

你可以像这样做:

template <class T>
class wnd {
public:
    T* set_id(int id) { id_ = id; return (T*)this; }
    T* set_text(std::string text) { text_ = text; return (T*)this; }
protected:
    int id_;
    std::string text_;
};
template<class Derivate>
class app_wnd : public wnd<Derivate> {
public:
    app_wnd* show() { std::cout << wnd<app_wnd<Derivate>>::text_ << " " << wnd<app_wnd<Derivate>>::id_; return (Derivate*) this; }
};
class main_wnd : public app_wnd<main_wnd> {
public:
    main_wnd* set_app_instance(int instance) { instance_ = instance; return (main_wnd *) this; }
protected :
    int instance_;
};
int main(){
    (new main_wnd())->set_id(0)->set_app_instance(10);
}

(受CRTP启发)。

然而,您将无法再实例化app_wnd了。


只要我们知道最终的类是什么,这就可以工作。然而,如果我们有一个按钮类(这是很可能的),它可能是final的,或者我们可以从它派生出custom_button。 - Tomaz Stih

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接