我们可以继承Qt容器吗? [英] Can we inherit from Qt containers?

查看:176
本文介绍了我们可以继承Qt容器吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们应该能够继承Qt容器,例如 QList QVector QMap 以便专门化它们并添加功能?如果是这样,我们需要做什么,以确保容器正常工作(虚拟方法,虚拟析构函数等?)。

解决方案

两者,STL和Qt容器选择非虚拟析构函数。



有一个有趣的讨论为什么这是,为什么它不固定与Qt5。





此外,请注意STL样式和Qt容器之间的进一步差异。引用Jens Weller在他的博客文章介绍Qt < a>:


然而,Qt容器和STL容器之间有一个重要的区别:Qt容器有值语义,仅在写时执行复制,而std容器在复制时将复制其全部内容。这个行为解释了大多数Qt基类,他们将只在需要时为数据创建一个新的实例。


您的选择是一如既往:






  • b
    $ b

      struct MyFancyList 
    {
    QList< MyType> _数据;

    bool frobnicate(){return true; }
    };


  • 自由功能



    扩展QList与非成员操作:

      template< typename> 
    {
    //你的算法
    return true;
    }




做一些funky的东西,比如创建一个隐式转换或重载一个成员操作符,你可以诉诸表达式模板。



更新:后者也是新版本中由 QStringBuilder





奖金



有趣的是,这里有一个表达式模板来扩展 std :: stack< T> 的接口。查看 Live on Coliru ideone



我们都知道, std :: stack 不对顺序容器建模,因此没有 begin() end ) operator [] 定义。有了一点hackery,我们可以定义一个eDSL来提供这些功能,没有组合或继承。



真正的驱动器回家,你可以'超载'行为我们将使得你可以隐式地将 extend(stack)[n] 的结果转换为 std :: string ,即使堆栈包含eg int

  #include< string& 
#include< stack>
#include< stdexcept>

命名空间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){}

操作符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);
}

运算符std :: string()const {
// TODO使用`boost :: lexical_cast`来允许更宽的范围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<< \\\
;
}

Voila。更疯狂:

  auto magic = extend 
std :: cout<< magic [0]< \\\
;
std :: cout<< magic [1]<< \\\
;
std :: cout<< magic [2]< \\\
;
std :: cout<< magic [3]<< \\\
;

double as_double = magic [0];
std :: string as_string = magic [0];

列印

  0.5 
0.6
0.7
0.8

strong>免责声明


  1. 我知道 std :: stack 有一个限制接口的原因。

  2. 我知道我的索引执行具有可怕的效率。

  3. 我知道隐含的转换是邪恶的。这只是一个有价值的例子。

  4. 在现实生活中,使用Boost :: Proto获取DSL。

看看 QStringBuilder 用于更现实的样本。


Are we supposed to be able to inherit from Qt containers such as QList, QVector or QMap in order to specialize them and add functionality? If so, what do we need to do in order to be sure that the container works correctly (virtual methods, virtual destructor etc..?). If not, why not and what other options do I have?

解决方案

Both, STL and Qt Containers opt for non virtual destructors.

There is an interesting discussion why this is, and why its not fixed with Qt5.

Also, note further differences between STL style and Qt containers. Quoting Jens Weller in his Blog post An introduction into Qt:

Still, there is an important difference between Qt containers and STL containers: Qt containers have value semantics, and will only perform copy on write, while a std container will copy its full contents when copied. This behavoir accounts for most of Qt base classes, that they will only create a new instance for data, when needed. This implicit sharing of resources is a very important concept to understand when dealing with Qt and its containers.

Your options are as always:

  • composition

    E.g.

     struct MyFancyList
     { 
            QList<MyType> _data;
    
            bool frobnicate() { return true; }
     };
    

  • free functions

    E.g. extend QList with non-member operations:

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

If you really wanted to do funcky stuff, like create an implicit conversion or overload a member operator, you could resort to expression templates.

Update: the latter is also the approach taken by QStringBuilder in new versions. See

Bonus

For fun, here's a (bad!) illustration of how you could use expression templates to extend the interface of std::stack<T>. See it Live on Coliru or ideone

As we all know, std::stack doesn't model a sequential container, and therefore doesn't have begin(), end(), or operator[] defined. With a bit of hackery, we can define a eDSL to provide these features, without composition or inheritance.

To really drive the point home that you can 'overload' behaviour of the wrapped class in essential ways, we'll make it so that you can implicitly convert the result of extend(stack)[n] to a std::string, even if the stack contains e.g. 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 };
        }
    };
}

Now all we have to do is seed our expression templates. We'll use a helper function that wraps any std::stack:

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

Ideally, our users never realize the exact types inside exprtemplates namespace:

#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";
}

Voila. More craziness:

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];

Prints

0.5
0.6
0.7
0.8

DISCLAIMERS

  1. I know std::stack has a restrictive interface for a reason.
  2. I know that my indexing implementation has horrific efficiency.
  3. I know that implicit conversions are evil. This is just a contrived example.
  4. In real life, use Boost::Proto to get a DSL going. There are many pitfalls and gotchas in doing all the mechanics by hand.

Look at QStringBuilder for a more real life sample.

这篇关于我们可以继承Qt容器吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆