5

背景

我有一个在内部使用 vector<std::string> 的容器类。我为这个包装类提供了一个方法AddChar(std::string) ,它对内部向量执行push_back() 。在我的代码中,有时我必须向容器中添加多个项目。为此我必须使用

container.AddChar("First");
container.AddChar("Second");

这使得代码更大。所以为了让它更容易,我打算重载操作符<<。这样我就可以写了

container << "First" << "Second"

两个项目将被添加到基础向量中。

这是我用来做的代码

class ExtendedVector
{
private:
    vector<string> container;

public:
    friend ExtendedVector& operator<<(ExtendedVector& cont,const std::string str){
        cont.AddChar(str);
        return cont;
    }

    void AddChar(const std::string str)
    {
        container.push_back(str);
    }

    string ToString()
    {
        string output;
        vector<string>::iterator it = container.begin();
        while(it != container.end())
        {
            output += *it;
            ++it;
        }
        return output;
    }
};

它按预期工作。

问题

  1. 运算符重载是否正确编写?
  2. 在这种情况下重载运算符是一种好习惯吗?
  3. 这段代码会有任何性能问题或任何其他问题吗?

有什么想法吗?

编辑

在听到优秀的评论后,我决定不超载 << 因为它在这里没有意义。我删除了运算符重载代码,这是最终代码。

class ExtendedVector
{
private:
    vector<string> container;

public:

    ExtendedVector& AddChar(const std::string str)
    {
        container.push_back(str);
        return *this;
    }

         .. other methods
}

这允许我添加

container.AddChar("First").AddChar("Second")

在 C# 中,我可以通过使用 params 关键字更轻松地做到这一点。代码会像

void AddChar(params string[] str)
{
    foreach(string s in str)
       // add to the underlying collection
}

我知道在 C++ 中,我们可以使用...来指定参数的可变长度。但是 AFAIK,它不是类型安全的。那么这样做是推荐的做法吗?这样我就可以写了

container.AddChar("First","Second")

感谢您的回复。

4

7 回答 7

8

运算符重载是否正确编写?

是的,但可以做得更好。就像其他人提到的那样,您的函数可以完全由现有的公共函数定义。为什么不让它只使用那些?现在,它是一个朋友,这意味着它属于实现细节。如果您将 operator<< 作为成员放入您的班级,情况也是如此。但是,让您的 operator<< 成为非会员非朋友的功能。

class ExtendedVector {
    ...
};

// note, now it is *entirely decoupled* from any private members!
ExtendedVector& operator<<(ExtendedVector& cont, const std::string& str){
    cont.AddChar(str);
    return cont;
}

如果您更改课程,您将无法确定您的 operator<< 是否仍然有效。但是,如果您的 operator<< 完全仅依赖于公共函数,那么您可以确定它仅在对您的类的实现细节进行更改后才会起作用。耶!

在这种情况下重载运算符是一种好习惯吗?

正如另一个人再次所说,这是有争议的。在许多情况下,运算符重载乍一看看起来很“整洁”,但明年看起来就像地狱一样,因为当你给一些符号特别的爱时,你已经不知道你在想什么了。在运算符<<的情况下,我认为这是一个不错的用途。它用作流的插入运算符是众所周知的。我知道 Qt 和 KDE 应用程序在以下情况下广泛使用它

QStringList items; 
items << "item1" << "item2";

类似的情况是boost.format,它也重operator%用于在其字符串中为占位符传递参数:

format("hello %1%, i'm %2% y'old") % "benny" % 21

当然,在那里使用它也是有争议的。但是它对 printf 格式指定的使用是众所周知的,因此它的使用在那里也可以,恕我直言。但与往常一样,风格也是主观的,所以要持保留态度:)

如何以类型安全的方式接受可变长度参数?

好吧,如果您正在寻找同质参数,则有一种接受向量的方法:

void AddChars(std::vector<std::string> const& v) {
    std::vector<std::string>::const_iterator cit =
        v.begin();
    for(;cit != v.begin(); ++cit) {
        AddChar(*cit);
    }
}

但是通过它并不是很舒服。你必须手动构建你的向量然后通过......我看到你已经对可变参数样式函数有了正确的感觉。不应将它们用于此类代码,并且仅在与 C 代码或调试函数交互时才可使用。处理这种情况的另一种方法是应用预处理程序编程。这是一个高级主题,非常hacky。这个想法是自动生成达到某个上限的重载,大致如下:

#define GEN_OVERLOAD(X) \
void AddChars(GEN_ARGS(X, std::string arg)) { \
    /* now access arg0 ... arg(X-1) */ \
    /* AddChar(arg0); ... AddChar(arg(N-1)); */ \
    GEN_PRINT_ARG1(X, AddChar, arg) \
}

/* call macro with 0, 1, ..., 9 as argument
GEN_PRINT(10, GEN_OVERLOAD)

那是伪代码。您可以在此处查看 boost 预处理器库。

下一个 C++ 版本将提供更好的可能性。可以使用初始化列表:

void AddChars(initializer_list<std::string> ilist) {
    // range based for loop
    for(std::string const& s : ilist) {
        AddChar(s);
    }
}

...
AddChars({"hello", "you", "this is fun"});

在下一个 C++ 中也可以使用可变参数模板支持任意多个(混合类型)参数。GCC4.4 将支持它们。GCC 4.3 已经部分支持它们。

于 2009-03-02T04:35:14.243 回答
3

在这种情况下重载运算符是一种好习惯吗?

我不这么认为。对于不知道您已使运算符超载的人来说,这简直令人困惑。只需坚持描述性的方法名称,忘记您输入的额外字符,这不值得。您的维护者(或 6 个月后您自己)会感谢您。

于 2009-03-02T03:55:40.233 回答
3

1)是的,除非因为 AddChar 是公开的,所以没有理由它需要是friend.

2)这是有争议的。<<有点像是操作员,其“奇怪”事物的重载至少被勉强接受。

3)没有什么明显的。与往常一样,分析是您的朋友。您可能需要考虑将字符串参数传递给const reference ( )AddCharoperator<<通过 const reference ( const std::string&) 来避免不必要的复制。

于 2009-03-02T03:56:04.587 回答
2

我不希望以这种方式重载它,因为向量通常没有重载的左移运算符 - 这不是真的成语;-)

我可能会从 AddChar 返回一个引用,而不是像这样:

ExtendedVector& AddChar(const std::string& str) {
    container.push_back(str);
    return *this;
}

所以你可以这样做

container.AddChar("First").AddChar("Second");

这并不比位移运算符大多少。

(另请参阅 Logan 关于通过引用而不是按值传递字符串的评论)。

于 2009-03-02T04:07:08.787 回答
2

在这种情况下,运算符重载不是好的做法,因为它会降低代码的可读性。出于充分的理由,该标准std::vector也没有用于推动元素。

如果你担心调用者代码太长,你可以考虑这个而不是重载运算符:

container.AddChar("First").AddChar("Second");

AddChar()如果您有return ,这将是可能的*this

你有这个toString()功能很有趣。在这种情况下,operator<<输出到流将是标准的东西!因此,如果您想使用运算符,请将toString()函数设为operator<<.

于 2009-03-02T04:13:22.830 回答
1

运算符在这里没有正确重载。没有理由让操作员成为朋友,因为它可以是类的成员。Friend 用于不是类的实际成员的函数(例如,当为 ostream 重载 << 以便可以将对象输出到 cout 或 ofstreams 时)。

您实际希望操作员是什么:

ExtendedVector& operator<<(const std::string str){
    AddChar(str);
    return *this;
}

通常认为以一种让它们做一些比正常情况下的操作的方式重载操作符是不好的做法。<< 通常是位移,因此以这种方式重载它可能会令人困惑。显然 STL 重载 << 用于“流插入”,因此以类似的方式重载它以供您使用可能是有意义的。但这似乎不像你在做什么,所以你可能想避免它。

没有性能问题,因为运算符重载与常规函数调用相同,只是调用被隐藏了,因为它是由编译器自动完成的。

于 2009-03-02T04:08:11.547 回答
0

这会让事情变得相当混乱,我会使用与 std::cin 相同的语法到变量中:

std::cin >> someint;

"First" >> container;

这样,它至少是一个插入运算符。对我来说,当任何东西都有一个 << 重载运算符时,我希望它会输出一些东西。就像 std::cout 一样。

于 2009-03-02T04:23:23.100 回答