2009-03-02 118 views
5

背景重载operator << - C++

我有一个使用矢量<的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 
} 

这让我通过使用params关键字添加

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

在C#中,我可以做到这一点更容易。代码会像

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

我知道在C++中,我们可以使用...指定的参数变量langth。但是AFAIK,它不是类型安全的。那么这是一个推荐的做法吗?所以我可以写

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

感谢您的答复。

+0

Qt使用operator <<为QStringList:http://doc.trolltech.com/4.4/qstringlist.html,我喜欢它的使用。但通常我会注意不要添加太多的操作员。正如凯文所说,它可能会变得混乱如地狱:) – 2009-03-02 04:09:38

回答

8

运算符过载是否正确写入?

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

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

如果你改变你的类,你不会相信你的运营商< <仍然有效。但是,如果您的运营商仅完全依赖公共功能,则只有在对您的类的实施细节进行了更改后,才能确定它将起作用。好极了!

在这种情况下重载操作符是否是一种很好的做法?

另一个人再次说,这是有争议的。在很多情况下,操作员超载会一眼看上去“整洁”,但明年看起来会像地狱一样,因为当你给某些符号特别的爱时,你不会再想到你心中的想法。在运营商< <的情况下,我认为这是确定的用途。它用作流的插入运算符是众所周知的。而且我知道的是广泛使用的情况下,像

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

一个类似的例子是boost.format这也重用operator%传递参数的占位符的字符串Qt和KDE应用程序:

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

这不是一个真正舒适虽则通过。你必须手动构建你的矢量,然后通过...我发现你已经对可变参数样式函数有了正确的感觉。一个人不应该将它们用于这种类型的代码,只有在与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) 

这是伪代码。您可以看看升压预处理器库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已经部分支持它们。

3

在这种情况下将 运算符重载是否是一种很好的做法?

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

+0

我同意。运算符重载可能很酷,但是当你在几个月后试图维护代码时,即使你编写它,你也希望能够以最小的努力理解它的功能。清晰的代码比混乱的酷代码更好。 – 2009-03-02 04:02:18

+0

我同意。只要你不改变操作员的意思。 I.E. MyWebRequest =“url”;不应该实际执行Web请求。对于这样的事情,我更喜欢函数链或函数获取数组。 – Rahly 2016-08-24 00:39:56

3

1)是的,除了AddChar是公开的,没有理由需要成为friend

2)这是有争议的。 <<在某种程度上是一个运营商,它对于“怪异”事物的重载至少被勉强接受。

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

+0

对于std :: string引用+1,以避免复制。还有其他一切。 – Frank 2009-03-02 04:14:04

+0

谢谢。但是如果我删除朋友,编译器会抛出错误。 – 2009-03-02 04:16:59

+0

的确,我也给你+1了。我特别喜欢#1。但我会说清楚:“让它成为非会员非朋友功能”:)并且我也喜欢#2。而不是说“糟糕的坏”你提供了参数的基础:)而且我认为op <<被广泛接受为插入操作。 – 2009-03-02 04:19:17

2

我不希望这样超负荷往心里去,因为载体通常不会有一个重载的左移运算符 - 它不是真正的它的成语;-)

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

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

所以你可以再做

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

这是不是真的比位移操作符大得多。

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

+0

你打败我了!通过,呃,几分钟......;) – Frank 2009-03-02 04:17:29

1

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

你真正想要的操作是什么:

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

它通常被认为是不好的做法,超负荷运营商有他们做比他们做的事情,通常的方式。 < <通常是位移,因此以这种方式重载可能会造成混淆。很明显,STL为“流式插入”重载< <,因此可能对于以类似的方式使用它而言是有意义的。但是这看起来不像你在做什么,所以你可能想避免它。

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

2

在这种情况下,运算符重载不是很好的做法,因为它使代码不易读。标准std::vector不具备推送元素的原因。

如果你担心主叫代码太长,你可以代替重载运算符的考虑:

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

这将是可能的,如果你有AddChar()回报*this

有趣的是,你有这个toString()函数。在的情况下,一个operator<<输出到一个流将是标准的东西,而不是!所以如果你想使用运营商使toString()功能operator<<

0

这会让情况变得相当混乱,我会用同样的语法的std :: CIN到一个变量:

std::cin >> someint;

"First" >> container;

这样它至少是一个插入运营商。对我来说,当任何事情有一个< <重载的运营商,我希望它是输出的东西。就像std :: cout。