3

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

4

1 回答 1

6

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

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

另外,请注意 STL 样式和 Qt 容器之间的进一步差异。在他的博客文章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<T>. 在 Coliru或ideone上观看

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

为了真正强调您可以以基本方式“重载”被包装类的行为,我们将这样做,以便您可以隐式地将结果转换extend(stack)[n]为 a std::string,即使堆栈包含 eg 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 };
        }
    };
}

现在我们要做的就是为我们的表达式模板播种。我们将使用一个包装 any 的辅助函数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更真实的生活样本。

于 2013-07-29T16:56:26.880 回答