重载运算符<< - C ++

时间:2009-03-02 03:44:38

标签: c++ vector operator-overloading params-keyword

背景

我有一个容器类,它使用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. 此代码是否存在任何性能问题或任何其他问题?
  4. 有什么想法吗?

    修改

    在听到优秀评论后,我决定不重载&lt;&lt;因为这里没有意义。我删除了操作符重载代码,这是最终代码。

    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")
    

    感谢您的回复。

7 个答案:

答案 0 :(得分:8)

  

是否正确写入了运算符重载?

确实如此,但人们可以做得更好。与其他人提到的一样,您的功能可以完全由现有的公共功能定义。为什么不让它只使用那些?现在,它是一个朋友,这意味着它属于实现细节。如果你把运算符&lt;&lt;&lt;&lt;&lt;&lt;作为你班上的一员。但是,请使您的运营商&lt;&lt; 非会员非朋友功能。

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

如果您更改了课程,则无法确定您的操作员&lt;&lt;仍然有效。但是,如果您的运营商&lt;&lt;完全取决于公共函数,那么只有在对类的实现细节进行更改后,才能确保它能够正常工作。耶!

  

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

另一个人再说一遍,这是有争议的。在许多情况下,操作员超载一见钟情看起来很“整洁”,但明年看起来会像地狱一样,因为在给予一些符号特别的爱时,你不再知道你的想法。在运营商&lt;&lt;的情况下,我认为这是一个好的用途。它用作流的插入操作符是众所周知的。我知道在像

这样的情况下广泛使用它的Qt和KDE应用程序
QStringList items; 
items << "item1" << "item2";

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

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

在那里使用它当然也是有争议的。但它用于printf格式指定是众所周知的,所以它的使用也是可以的,imho。但是和往常一样,风格也是主观的,所以要带上一粒盐:)

  

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

嗯,如果你正在寻找同质论证,有接受矢量的方法:

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

传递它并不是很舒服。你必须手动构建你的矢量然后传递...我看到你已经对vararg风格函数有了正确的感觉。不应该将它们用于这种代码,只有在与C代码接口或调试函数时才使用它们。处理这种情况的另一种方法是应用预处理器编程。这是一个高级主题,非常黑客。我们的想法是自动生成一个大小超过某个上限的重载:

#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预处理器库here

下一个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 ++中,也可以使用variadic templates支持任意多个(混合类型)参数。 GCC4.4将为他们提供支持。 GCC 4.3已经部分支持它们。

答案 1 :(得分:3)

  

过载是一种好习惯   在这样的情况下运营商?

我不这么认为。对于那些不知道你已经超载运营商的人来说,这让人感到困惑。只需坚持描述性的方法名称,忘记你正在输入的额外字符,它就是不值得的。你的维护者(或你自己在6个月内)会感谢你。

答案 2 :(得分:3)

1)是的,但由于AddChar是公开的,所以没有必要成为friend

2)这是有争议的。 <<有点像运营商一样,对于“怪异”事物的超载至少是勉强接受的。

3)没有什么明显的。一如既往,剖析是你的朋友。您可能需要考虑通过const引用(AddChar)将字符串参数传递给operator<<const std::string&,以避免不必要的复制。

答案 3 :(得分:2)

我不希望以个人方式重载它,因为向量通常不会有一个超载的左移运算符 - 它不是真正的成语; - )

我可能会从AddChar返回一个引用,如下所示:

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

所以你可以这样做

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

并不比bitshift运算符大得多。

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

答案 4 :(得分:2)

在这种情况下,运算符重载并不是一种好习惯,因为它会降低代码的可读性。标准std::vector没有用于推送元素,原因很充分。

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

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

如果您AddChar()返回*this,则可以使用此功能。

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

答案 5 :(得分:1)

此处操作员未正确重载。没有理由让操作员成为朋友,因为它可以是班级的成员。 Friend用于不是类的实际成员的函数(例如当重载&lt;&lt;对于ostream时,对象可以输出到cout或ofstreams)。

您真正希望操作员:

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

通常认为以一种比正常情况更多的方式重载运算符是不好的做法。 &LT;&LT;通常是位移,因此以这种方式重载可能会令人困惑。显然STL重载&lt;&lt;对于“流插入”等等,可能有意义地重载它以便以类似的方式使用它。但这似乎不像你正在做的那样,所以你可能想避免它。

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

答案 6 :(得分:0)

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

std::cin >> someint;

"First" >> container;

这样它至少是一个插入操作符。当任何东西都有&lt;&lt;重载运算符我希望它输出一些东西。就像std :: cout。

相关问题