派生类(向量):这是个坏主意吗?

derived class (vector): is this a bad idea?

本文关键字:向量 派生      更新时间:2023-10-16

我即将为我正在编写的库"创建"我自己的向量类。我不会真的从头开始创建一个向量类,但只是使用std::vector作为父类,并添加一些东西到我的派生向量类。

话虽如此,我还是需要一些建议。我的问题是:

1)调用我的派生类vector(在另一个命名空间内,当然)是一个坏主意吗?在这种情况下会有什么样的冲突?

2)我想重载数学运算符,以便能够添加向量,将向量乘以常量等。这实际上是我决定在std::vector之上建立一个矢量类的原因。不过,我可以直接重载std::vector的数学运算符,从而省去创建(又一个……)向量类的麻烦——但这是个坏主意吗?

3)我非常想继承std::vector的构造函数,但我在没有编译器错误的情况下完成这一点有很多麻烦。有人能给我提供一个具体的例子,告诉我如何做到这一点吗?

谢谢!

我劝你不要那样做。如果您需要一个新的容器,只需嵌入(或从私有继承) std::vector<>,而不是从它公开继承。

关于你的问题:

1)调用我的派生类向量(在另一个命名空间内,当然)是一个坏主意吗?在这种情况下会有什么样的冲突?

只要您和您的任何客户端都不会通过using指令导入std命名空间和您的命名空间,我不认为这有什么特别的问题。

2)我想重载数学运算符,以便能够添加向量,将向量乘以常量等。这实际上是我决定在std::vector之上构建vector类的原因。不过,我可以直接重载std::vector的数学运算符,从而省去了创建(又一个……)vector类的麻烦——但这是个坏主意吗?

是的,这是个坏主意。如果为std::vector创建了数学运算符的重载,则不能在std::vector所属的名称空间中创建它们。通过ADL,可能会在该名称空间中找到全局operator +,即使它不合适。如果是这种情况,你的operator +甚至不会被查找,你会得到一个编译器错误。

3)我非常想从std::vector继承构造函数,但我在没有编译器错误的情况下完成此操作时遇到了很多麻烦。有人能给我提供一个具体的例子,告诉我如何做到这一点吗?

在c++ 11中,可以使用继承构造函数。例如:

#include <vector>
template<typename T, typename Allocator = std::allocator<T>>
class myvector : public std::vector<T, Allocator>
{
    using typename std::vector<T, Allocator>::vector;
};
#include <iostream>
int main()
{
   std::vector<int> vi { 1, 2, 3, 4, 5};
   // Uses inherited constructor
   myvector<int> v(vi.begin(), vi.end());
   for (auto i : v) { std::cout << i << " "; };
}

下面是一个使用GCC 4.8.0 (beta)的实例,它支持继承构造函数。

首先,如果您愿意,请注意您可以混淆继承。使用private继承,然后使vector的所有功能都可以通过using语句使用。它仍然是一个相当数量的类型,因为vector有很多,但它不像显式地委托每个函数,它防止了继承的常见异议,人们会意外地分割你的派生类和/或删除它使用错误的类型,当没有虚析构函数。无论如何,任何人都不应该删除vector

1)从名称空间std中使用相同的名称本身没有问题(毕竟这就是名称空间的用途),但有时可能会使读者感到困惑。他们可能会认为你已经在某个地方完成了using std::vector;。这是他们自己的错,但如果你能想出另一个合适的名字,那么惩罚读者的错误就没有什么好处了。

2)可以重载标准类的操作符,但不能重载std命名空间中的操作符。这就留下了一个问题:应该在什么范围内重载它们。一般来说,您不希望将内容放入头文件的全局作用域中,但是您可以将它们放入名为vector_arithmetic的名称空间中,然后将using namespace vector_arithmetic;放入您想要使用它们的每个作用域中。仅仅因为你可以这样做并不意味着它是明智的——读者希望知道哪些操作符可以用于像vector这样的常见类,而改变可用操作符的集合可能会吓到他们并扰乱他们。

3)如果你没有c++ 11构造函数继承,你就不能继承构造函数。相反,您需要费力地编写自己的构造函数,这些构造函数接受正确的参数,并在初始化列表中调用适当的基类构造函数。标准(或其他文档)将告诉您所需的所有构造函数签名。要特别注意模板化构造函数,并注意c++ 03和c++ 11中的构造函数是不一样的。

最后,考虑只写一些自由函数:

template <typename T>
T sequence_add(const T &lhs, const T &rhs) {
    if (rhs.size() != lhs.size()) throw std::logic_error("size mismatch");
    T result(lhs.size());
    std::transform(lhs.begin(), lhs.end(), rhs.begin(), result.begin(),
        std::plus<typename T::value_type>()
    );
    return result;
}
std::vector<int> i,j;
std::vector<int> k = sequence_add(i,j);

等等。如果操作符重载会引起混淆,那么它们通常是不值得的。

1)在您的命名空间下编写您自己的向量。但是,尽量不要从stl派生。

2,3)忘记std::vector,编写自己的方法和操作符。

同样,您可以将std::vector包装在您的新类中,并编写接口来操作它。