【问题标题】:Can we inherit from Qt containers?我们可以从 Qt 容器继承吗?
【发布时间】:2013-07-29 16:53:08
【问题描述】:

我们是否应该能够继承 Qt 容器,例如 QList、QVector 或 QMap,以便专门化它们并添加功能?如果是这样,我们需要做什么才能确保容器正常工作(虚拟方法、虚拟析构函数等?)。如果没有,为什么不呢?我还有哪些其他选择?

【问题讨论】:

    标签: c++ qt containers


    【解决方案1】:

    STL 和 Qt 容器都选择非虚拟析构函数。

    有一个有趣的讨论为什么会这样,以及为什么它没有被 Qt5 修复。

    另外,请注意 STL 样式和 Qt 容器之间的进一步差异。在他的博客文章An introduction into Qt 中引用 Jens Weller:

    Qt 容器和 STL 容器之间还有一个重要区别:Qt 容器具有值语义,并且只会在写入时执行复制,而 std 容器在复制时会复制其全部内容。这种行为解释了大多数 Qt 基类,它们只会在需要时为数据创建一个新实例。在处理 Qt 及其容器时,这种隐含的资源共享是一个非常重要的概念。

    您的选择一如既往:

    • 作曲

      例如

       struct MyFancyList
       { 
              QList<MyType> _data;
      
              bool frobnicate() { return true; }
       };
      
    • 免费功能

      例如使用非成员操作扩展 QList:

      template <typename> bool frobnicate(QList<T>& list)
      {
           // your algorithm
           return true;
      }
      

    如果你真的想做一些古怪的事情,比如创建隐式转换或重载成员运算符,你可以求助于表达式模板。

    更新:后者也是QStringBuilder在新版本中采用的做法。见

    奖金

    为了好玩,这里有一个(糟糕!)说明如何使用表达式模板来扩展std::stack&lt;T&gt; 的界面。见Live on Coliru或ideone

    众所周知,std::stack 没有模拟顺序容器,因此没有定义begin()、end() 或operator[]。通过一些技巧,我们可以定义一个 eDSL 来提供这些功能,而无需组合或继承。

    为了真正强调您可以以基本方式“重载”被包装类的行为,我们将这样做,以便您可以将 extend(stack)[n] 的结果隐式转换为 std::string,即使堆栈包含例如int.

    #include <string>
    #include <stack>
    #include <stdexcept>
    
    namespace exprtemplates
    {
        template <typename T> struct stack_indexer_expr
        {
            typedef std::stack<T> S;
            S& s;
            std::size_t n;
            stack_indexer_expr(S& s, std::size_t n) : s(s), n(n) {}
    
            operator T() const {
                auto i = s.size()-n; // reverse index
                for (auto clone = s; !clone.empty(); clone.pop())
                    if (0 == --i) return clone.top();
                throw std::range_error("index out of bounds in stack_indexer_expr");
            }
    
            operator std::string() const {
                // TODO use `boost::lexical_cast` to allow wider range of T
                return std::to_string(this->operator T());
            }
        };
    
        template <typename T> struct stack_expr
        {
            typedef std::stack<T> S;
            S& s;
            stack_expr(S& s) : s(s) {}
    
            stack_indexer_expr<T> operator[](std::size_t n) const {
                return { s, n };
            }
        };
    }
    

    现在我们要做的就是seed我们的表达式模板。我们将使用包装任何std::stack 的辅助函数:

    template <typename T> 
    exprtemplates::stack_expr<T> extend(std::stack<T>& s) { return { s }; }
    

    理想情况下,我们的用户永远不会意识到 exprtemplates 命名空间中的确切类型:

    #include <iostream>
    int main()
    {
        std::stack<double> s;
        s.push(0.5);
        s.push(0.6);
        s.push(0.7);
        s.push(0.8);
    
        std::string demo = extend(s)[3];
        std::cout << demo << "\n";
    }
    

    瞧。更疯狂:

    auto magic = extend(s);
    std::cout << magic[0] << "\n";
    std::cout << magic[1] << "\n";
    std::cout << magic[2] << "\n";
    std::cout << magic[3] << "\n";
    
    double      as_double = magic[0];
    std::string as_string = magic[0];
    

    打印

    0.5
    0.6
    0.7
    0.8
    

    免责声明

    1. 我知道 std::stack 有一个限制性界面是有原因的。
    2. 我知道我的索引实现效率非常高。
    3. 我知道隐式转换是邪恶的。这只是一个人为的例子。
    4. 在现实生活中,使用 Boost::Proto 来启动 DSL。手工完成所有机制存在许多陷阱和陷阱。

    查看QStringBuilder 以获得更真实的示例。

    【讨论】:

    • 不知道构造函数可以是虚拟的。
    • @Jeffrey 析构函数应该对于任何可以多态使用的类都是虚拟的。否则,通过基类指针删除会导致 UB(泄漏和更糟)
    • 啊 :) 我太快了。固定
    • 那么总体答案是否,对吧?如果是这样,“我还有什么其他选择”?
    • @Jeffrey 那将是我的建议。但是,请参阅 Qt5 在该主题中的细微差别。具体来说,QStringList 是派生自 QList 的类的示例
    猜你喜欢
    • 1970-01-01
    • 2014-11-09
    • 2014-01-09
    • 1970-01-01
    • 2023-03-28
    • 2011-09-22
    • 2015-10-19
    • 1970-01-01
    相关资源
    最近更新 更多