我经常在SO上看到有关在C ++中重载逗号运算符的问题(主要与重载本身无关,但类似序列点的概念),这让我感到奇怪:
什么时候应该使逗号超载?其实际用途有哪些例子?
我只是想不出我已经看到或需要的任何例子,例如
foo, bar;
在现实世界的代码中,所以我很好奇它何时被使用。
Answers:
让我们将重点更改为:
当要你重载逗号?
答案:从不。
例外:如果您正在执行模板元编程,请operator,
在运算符优先级列表的最底部放置一个特殊位置,该位置对于构造SFINAE-guards等非常有用。
我所看到的关于重载的仅有的两个实际用途operator,
都是在Boost中:
operator,
吗?听起来真的很有趣。
我使用逗号运算符来索引具有多个索引的地图。
enum Place {new_york, washington, ...};
pair<Place, Place> operator , (Place p1, Place p2)
{
return make_pair(p1, p2);
}
map< pair<Place, Place>, double> distance;
distance[new_york, washington] = 100;
operator[]
。有人提出可以采用以下几个参数:参见Evolution Defect Report 88。
distance[{new_york, washington}]
在不过载的情况下工作。额外的一组支架要付出很小的代价,才能避免发生如此邪恶的事情!
foo(new_york, washington)
,该函数应将两个单独的位置作为参数会发生什么?
Boost.Assign使用它来执行以下操作:
vector<int> v;
v += 1,2,3,4,5,6,7,8,9;
而且我已经看到它用于古怪的语言破解,我看看是否能找到一些。
啊哈,我确实记得其中一种古怪的用法:收集多个表达式。(警告,黑暗魔法。)
push_back
这8个值中a的简写,但看起来好像9被添加到了vector<int>
,这没有任何意义。坦率地说,对于Boost是“高质量库”,这是一个强烈的反驳。代码应该清晰明了。否则,也可以实现类似的功能T& operator--(int){ delete this; return *this; }
,也可能会很好地工作。对于其他人而言,这并不明显。
operator<=
。这样您就可以编写像这样的很棒的代码str <= "foo";
。当下一个阅读您的代码的人说“到底是什么?”时,这一点都不酷。并且第一次花一周的时间进行无益调试变得完全不酷,因为有人不知道并写了这样的东西if(str <= "bar")
。
逗号具有一个有趣的属性,它可以采用type参数void
。如果是这种情况,则使用内置的逗号运算符。
当您要确定表达式的类型是否为void时,这很方便:
namespace detail_
{
template <typename T>
struct tag
{
static T get();
};
template <typename T, typename U>
tag<char(&)[2]> operator,(T, tag<U>);
template <typename T, typename U>
tag<U> operator,(tag<T>, tag<U>);
}
#define HAS_VOID_TYPE(expr) \
(sizeof((::detail_::tag<int>(), \
(expr), \
::detail_::tag<char>).get()) == 1)
我让读者弄清楚正在发生的事情。记住operator,
右边的那个。
在SOCI-C ++数据库访问库中,它用于实现接口的入站部分:
sql << "select name, salary from persons where id = " << id,
into(name), into(salary);
问:重载的逗号运算符只是一种混淆,我不喜欢它。
好吧,请考虑以下几点:
“将查询X发送到服务器Y并将结果放入变量Z。”
在上方,“和”起逗号的作用。即使重载逗号运算符在C ++中不是很流行的做法,某些库也会这样做,从而获得简洁易懂的语法。我们非常确定,在SOCI中,逗号运算符被重载了,并且效果很好。
我使用逗号运算符来打印日志输出。它实际上与之非常相似,ostream::operator<<
但是我发现逗号运算符实际上更适合该任务。
所以我有:
template <typename T>
MyLogType::operator,(const T& data) { /* do the same thing as with ostream::operator<<*/ }
它具有这些不错的特性
逗号运算符的优先级最低。因此,如果您要流式传输表达式,则忘记括号不会使事情变得混乱。相比:
myLog << "The mask result is: " << x&y; //operator precedence would mess this one up
myLog, "The result is: ", x&y;
您甚至可以毫无问题地将比较运算符混在其中,例如
myLog, "a==b: ", a==b;
逗号运算符在视觉上很小。将许多东西粘合在一起时不会与阅读混淆
myLog, "Coords=", g, ':', s, ':', p;
它符合逗号运算符的含义,即“先打印”然后“先打印”。
一种可能是Boost Assign库(尽管我很确定某些人会认为这种滥用而不是一种很好的用法)。
Boost Spirit可能还会使逗号运算符超载(几乎使所有其他内容超载...)
同样,我收到了一个带有逗号运算符重载的github pull请求。看起来像以下
class Mylogger {
public:
template <typename T>
Mylogger & operator,(const T & val) {
std::cout << val;
return * this;
}
};
#define Log(level,args...) \
do { Mylogger logv; logv,level, ":", ##args; } while (0)
然后在我的代码中我可以做:
Log(2, "INFO: setting variable \", 1, "\"\n");
有人可以解释为什么这是好事还是坏事?
... << "This is a message on line " << std::to_string(__LINE__) << " because variable a = " << std::to_string(a) << " which is larger than " << std::to_string(limit) << "\n"
。在报告错误或生成异常消息时非常常见。我不知道如果逗号是唯一的选择:任何其他运营商可能已经实现了这个,例如operator+
或operator|
或者operator&&
甚至operator<<
本身。但这是一个有趣的案例。
一种实际用法是有效地将其与宏中的可变参数一起使用。顺便说一句,可变参数是GCC的早期扩展,现在是C ++ 11标准的一部分。
假设我们有一个class X
,它将类型的对象添加A
到其中。即
class X {
public: X& operator+= (const A&);
};
如果我们想要1个或多个对象添加A
到X buffer;
?
例如,
#define ADD(buffer, ...) buffer += __VA_ARGS__
上面的宏,如果用作:
ADD(buffer, objA1, objA2, objA3);
然后它将扩展为:
buffer += objA1, objeA2, objA3;
因此,这将是使用逗号运算符的完美示例,因为变量参数会随之扩展。
因此,要解决此问题,我们需要重载comma
运算符并将其包装+=
如下
X& X::operator, (const A& a) { // declared inside `class X`
*this += a; // calls `operator+=`
}
template<typename ... A> X& ADD(X& buff, A ... args) { int sink[]={ 0,(void(buff+=args),0)... }; return buff;}
。注意:您可能必须防止使用(void) sink;
语句来优化接收器。这闪避了宏,它是imo,甚至更好
这是OpenCV文档(http://docs.opencv.org/modules/core/doc/basic_structures.html#mat)中的示例。逗号运算符用于cv :: Mat初始化:
// create a 3x3 double-precision identity matrix
Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);