我们可以从 Qt 容器继承吗?
我们是否应该能够继承 Qt 容器,例如 QList
、QVector
或 QMap
以便专门化它们并添加功能?如果是这样,我们需要做什么才能确保容器正常工作(虚拟方法、虚拟析构函数等?).如果没有,为什么不呢?我还有哪些其他选择?
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?
推荐答案
STL 和 Qt 容器都选择非虚拟析构函数.
Both, STL and Qt Containers opt for non virtual destructors.
有一个有趣的讨论为什么会这样,以及为什么它没有被 Qt5 修复.
There is an interesting discussion why this is, and why its not fixed with Qt5.
- QList 没有虚析构函数,而是继承自 http:///qt-project.org/forums/viewthread/16416
另外,请注意 STL 样式和 Qt 容器之间的进一步差异.在他的博客文章中引用 Jens Weller 对 Qt 的介绍一个>:
Also, note further differences between STL style and Qt containers. Quoting Jens Weller in his Blog post An introduction into Qt:
Qt 容器和 STL 容器之间还有一个重要区别:Qt 容器具有值语义,并且只会在写入时执行复制,而 std 容器在复制时会复制其全部内容.这种行为解释了大多数 Qt 基类,它们只会在需要时为数据创建一个新实例.在处理 Qt 及其容器时,这种隐式的资源共享是一个非常重要的概念.
您的选择一如既往:
作曲
例如
struct MyFancyList
{
QList<MyType> _data;
bool frobnicate() { return true; }
};
免费功能
free functions
例如使用非成员操作扩展 QList:
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.
更新:后者也是QStringBuilder
在新版本中采用的方式.见
Update: the latter is also the approach taken by QStringBuilder
in new versions. See
- 讲座:表达式模板 (视频, 幻灯片) 作者:Volker Krause
- Lecture: Expression Templates (video, slides) by Volker Krause
为了好玩,这里有一个(糟糕!)说明如何使用表达式模板来扩展 std::stack<T>
的接口.查看 Live on Coliru 或 ideone
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
众所周知,std::stack
没有对 顺序容器 建模,因此没有 begin()
、end()
或 operator[]
定义.通过一些技巧,我们可以定义一个 eDSL 来提供这些功能,而无需组合或继承.
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.
为了真正强调您可以以基本方式重载"被包装类的行为,我们将这样做,以便您可以隐式转换 extend(stack)[n] 的结果
到 std::string
,即使堆栈包含例如int
.
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 };
}
};
}
现在我们要做的就是seed我们的表达式模板.我们将使用一个辅助函数来包装任何 std::stack
:
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 }; }
理想情况下,我们的用户永远不会意识到 exprtemplates
命名空间中的确切类型:
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 << "
";
}
瞧.更疯狂:
auto magic = extend(s);
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
免责声明
- 我知道
std::stack
有一个限制性接口是有原因的. - 我知道我的索引实现效率非常高.
- 我知道隐式转换是邪恶的.这只是一个人为的例子.
- 在现实生活中,使用 Boost::Proto 来运行 DSL.手工完成所有机制存在许多陷阱和陷阱.
- I know
std::stack
has a restrictive interface for a reason. - I know that my indexing implementation has horrific efficiency.
- I know that implicit conversions are evil. This is just a contrived example.
- In real life, use Boost::Proto to get a DSL going. There are many pitfalls and gotchas in doing all the mechanics by hand.
查看 QStringBuilder
以获得更真实的示例.
Look at QStringBuilder
for a more real life sample.
相关文章