问题很简单。 为什么编译:
1 2
| bool b(true);
if (b) { /* */ } |
并编译:
1
| if (bool b = true) { /* */ } |
但这不是:
1
| if (bool b(true)) { /* */ } |
在我的真实代码中,我需要构造一个对象并对其进行测试,同时还要在if块结束时销毁它。 基本上,我正在寻找这样的东西:
1 2 3 4 5 6
| {
Dingus dingus(another_dingus);
if (dingus) {
// ...
}
} |
当然,这可以工作:
1
| if (Dingus dingus = another_dingus) { /* */ } |
但是,然后我构造一个Dingus并对其调用operator=。 在我看来,我可以使用我喜欢的任何构造函数来构造对象。
但是我很困惑为什么这在语法上是不正确的。 我已经用G ++和MSVC ++进行了测试,他们都抱怨这种构造,所以我确定它是规范的一部分,但是我对此的原因以及可能存在的不丑陋的解决方法感到好奇。
-
"但是后来我构造了一个Dingus并在其上调用operator ="。不,不会发生分配。多数民众赞成在只是初始化(并碰巧使用=符号)。
-
您知道有条件的赋值被认为是非常非常糟糕的样式。而且它们很容易发生意外的编码错误。在某些语言中(例如C#或D),在条件组合中禁止分配。
-
@datenwolf:实际代码将以宏的形式进入,以获取资源并测试其有效性,如果无法获取资源,则优雅地转义。如果此构造有效,那么它将使我能够在简洁易懂的块式宏后面隐藏血腥的"不良样式"。
-
@GMan:然后扩展问题,如果我想显式调用构造函数,该怎么办? if (Dingus dingus = Dingus(...))?
-
@cdhowie:是的,正如Pubbys回答所指出的,那完全正确。
-
@GMan:正如另一个答案注释中指出的那样,如果编译器不使用构造函数省略,则使用=初始化可能会导致operator=调用。因此,如果Im试图在不支持该编译器的编译器上提高效率,则使用=初始化可能不是一个好选择。
-
@cdhowie:否,如果编译器没有删除副本,则使用=初始化会导致复制构造。无法将(operator=)分配给尚未构造的对象。
-
@ildjarn:对,我的理解是Dingus foo = bar;在语义上等同于Dingus foo; foo.operator=(bar);。我是否可以阅读规格说明来定义对对象使用=初始化时会发生什么?
-
@ildjarn:当然,并不是说我不信任你,我只是喜欢自己做研究。 (而且这个特定问题不是非常适合Google的。)
-
@cdhowie:C ++标准不是免费的,但是如果您不介意使用几乎与该标准相同的东西,则可以使用N3242。具体来说,您想阅读有关复制初始化与直接初始化(8.5)的信息。
-
@cdhowie:不,那是错的。 (1)T x(a)称为直接初始化。这意味着x是使用接受a的构造函数直接构造的。 (2)T x = y(a)称为复制初始化。 x是使用复制构造函数从y构造的,并且y是直接初始化的。 (3)在几乎所有情况下,编译器都会将复制初始化转换为直接初始化,因此性能无关紧要。唯一的问题是复制初始化要求T是可复制构造的,并非总是如此。
-
@Gman:即使禁用或未实现构造函数选择,这是否正确?
-
@cdhowie:哪一点是正确的? 1和2是标准要求的,3是每个现代编译器中的常见优化。 (请注意,MSVC做错了3次,或者在2008年做错了。这将使复制初始化转变为直接初始化,而无需检查T是否可复制。)
-
第3点。这是一项优化,但不是必需的。林说的是,不能假定if (Dingus dingus = foo)可以作为复制构造进行编译;对于编译器而言,将其实现为默认构造并随后赋值是合法的。
-
@cdhowie:The actual code will be going in a macro-然后停止流汗!没人需要看那个宏。只需用冗长的代码编写代码,再多加三行代码,然后喝杯啤酒-将整个shebang包裹在do { ..... } while(false)中,以保持宏大的理智并保持快乐
-
@sehe:我希望具有类似于WITH_RESOURCE(variablename) { ... }的语法,但是我可以找到的每种构造都将需要}}才能正确关闭块,或者需要在块末尾使用宏来隐藏该细节。我似乎找不到能够使我构造在块末尾销毁的对象,对该对象运行测试,如果无法获取资源就采取措施(在这种情况下记录警告)的措施,以及与MACRO() { }语法一起使用。我在尝试东西时注意到了这种特别的奇怪之处,并以为我打听了一下。 :)
-
@cdhowie也许您可以提出实际目标。我认为这个习语已经解决了很多次,尤其是在C ++ 11的存在下,几乎没有什么可以实现的
-
@cdhowie:嗯?编译器将T x = y转换为T x; x = y;从来都不合法,我不知道您从哪里得到的。
-
@sehe:我可能会单独回答一个问题。不幸的是,C ++ 11在此环境中不可用,因此我不能依靠它。
-
@GMan:那么构造函数的选择是做什么的?那是当您使用函数返回T类型的对象的结果来初始化T类型的变量时吗?
-
@cdhowie:那是一个领域,是的。这也意味着T x = T(a);可以成为T x(a);。
这有点技术性。没有理由不能允许您想要的东西,事实并非如此。这是语法。
if语句是选择语句,采用语法形式:
1
| if (condition) statement |
在这里,condition可以是:
-
expression或
-
type-specifier-seq declarator = assignment-expression
那里有它。允许在条件中进行声明是一种特殊情况,它必须遵循该格式,否则您的程序格式不正确。他们可能允许直接初始化而不是复制初始化,但是现在并没有任何动机。正如Johannes Schaub指出的那样,此更改将破坏现有代码,因此几乎永远不会发生。
Let_Me_Be指出C ++ 11添加了第三种形式(我在这里忽略属性):
1
| decl-specifier-seq declarator braced-init-list |
所以if (bool b{true})很好。 (这不可能破坏任何有效的现有代码。)
请注意,您的问题似乎与效率有关:请放心。编译器将忽略临时值,而直接直接构造左侧。但是,这要求您的类型是可复制的(或在C ++ 11中可移动)。
-
有趣的是,在C ++ 11中,if (bool x{true})似乎还可以(检查标准,实际上他们在条件规范中添加了第三行)。
-
我认为严格来说if (const noncopyable& nc = noncopyable(5))也是C ++ 11,因为-在C ++ 11之前-允许一个实现在绑定到const引用之前复制该临时文件。
-
@CharlesBailey:我认为您是对的,这有点泥泞。它可以,但是几乎从来没有(不需要)。而且我不确定津贴是否适用于参考资料。我非常确定T x = T(...)可以根据需要复制多次,但是我认为使用const T& x = T(...)引用必须立即绑定。但是我不确定这一切。
-
我已经检查了。它一点也不泥泞。 noncopyable(5)是一个右值,并且始终可以执行此操作:创建" cv1 T2类型的临时" [sic],并调用构造函数将整个右值对象复制到该临时中。该引用绑定到临时对象或临时对象内的子对象。"当将右值绑定到const ref时。
-
@CharlesBailey:呵呵,我以为那是一团糟,想不到。
-
noncopyable& nc = remove_const(noncopyable(5))创建一个悬空参考。
-
@ JohannesSchaub-litb:对,你呢?
-
我不明白这句话:"从技术上说,可以按照编译器的需要复制尽可能多的临时文件(在C ++ 03中),但是由于使用引用的目的是允许声明不可复制的对象,因此不必担心。"当然,他将在C ++ 03中获得"不可访问的副本构造函数"(即使没有完成复制),因此肯定会令人担心。还是我错过了重要的事情?
-
@ JohannesSchaub-litb:我是这里的傻瓜。 :(我肯定把它弄糊涂了很多。
应该注意的是,if(functor(f)(123)) ...;不再是带有参数123的匿名仿函数的调用,而是声明由123初始化的仿函数。
而且我认为为此小功能引入此类陷阱是不值得的。
由于可能不清楚上述含义,因此让我们更深入地了解一下。首先请记住,允许在声明符周围加上括号,包括在直接位于声明名称周围的简并情况下:
1 2 3 4 5
| int(n) = 0;
// same: int n = 0;
int(n)(0);
// same: int n(0); |
这两个带括号的版本都不明确,因为第一个可能是赋值,第二个可能是函数调用。但两者也可以是声明。标准说它们是声明。
如果我们允许在条件中使用paren初始化程序,那么我们也将后一种歧义引入条件中,就像语句情况一样。因此,在支持该功能之后,我们将使当今使用的有效条件表达式成为声明。考虑
1 2 3 4 5 6 7 8 9 10 11 12
| typedef bool(*handler_type)(int);
bool f(int) { /* ... */ }
bool f(int, int) { /* ... */ }
void call_it() {
// user wants to call f(int), but it is overloaded!
// -> user tries a cast...
if(handler_type(f)(0)) {
/* ... */
}
} |
您认为会发生什么?当然,它永远不会进入if主体,因为它总是声明一个空指针。它从不调用函数f。没有"功能",它将正确调用f,因为我们没有歧义。这不仅限于(f),还不仅限于(*f)(声明指针),(&f)(声明引用)等。
再说一遍:我们是否想要像这样的小功能价格那样的陷阱?我不知道有多少人甚至知道他们可以在某种情况下声明东西。
-
我不认为那是真的,因为f放在括号中。在if条件之外,此语法是明确的。为什么里面要模棱两可?
-
@cdhowie外面的语法也不明确。
-
然后,利用这种推理,启发我理解为什么该语法在用作语句而非条件时正确编译。
-
@cdhowie测试用例?我不知道您测试了什么代码...对于初学者,请尝试bool x; { bool(x)(123); }。
-
bool(x)(123)与bool x(123)不同。在后一种情况下,潜在的歧义是在函数原型和构造函数初始化之间。当编译器检查括号的内容时,这是消除歧义的。前一种情况是明确地尝试在由x构造的bool上调用operator()。
-
@cdhowie您没有尝试我的测试用例。不,前一种情况不是尝试调用bool,而是声明了bool变量x,并使用123对其进行了初始化。我对您忽略我在评论中写的内容感到不满意。
-
我知道,确实可以。但是,您反过来会忽略bool x(123)在条件以外的情况下也可以正常工作。它可能是模棱两可的,但是编译器能够通过查看上下文来解决歧义。换句话说,该表达式是存在于if()条件内部还是外部都不会使其变得模棱两可;编译器同样有能力在两种情况下消除表达式/语句的歧义。
-
@cdhowie我没有写任何有关bool x(123)的东西。我将修改答案(以前写在电话上),也许会更清楚。
-
嗯如果您不撰写有关typename variablename(...)表格的文章,那么这与问题有何关系?
-
好。我看到了问题,但不清楚如何在if()条件内或作为独立语句对它进行任何不同的评估。两种情况都存在歧义。
-
@cdhowie如果在if条件下允许paren初始化程序,则对它的评估不会有任何不同。当前不允许使用它们,因此bool(x)(123)在if条件下格式不正确,但作为陈述,它格式正确。
-
当然可以,但是如果在if条件下允许使用,为什么要对它进行不同的评估?
这是语言语法限制。 if语句中括号中的位可以是表达式,也可以是必须具有以下形式之一的受限声明形式:
attribute-specifier-seq OPT decl-specifier-seq declarator = initializer-clause
attribute-specifier-seq OPT decl-specifier-seq declarator braced-init-list
不允许其他形式的声明。请注意,这里没有分配,只有复制初始化。
如果要在select语句条件下直接初始化对象,则可以使用大括号初始化列表的新形式(自C ++ 11起):
1 2 3 4
| if (Type var { init })
{
// ...
} |
这是您问题的解决方案,尽管它不能完全回答问题:
1
| if (bool b = bool(true)) { /* */ } |
它没有执行您认为正在执行的操作-在这种情况下,bool(true)不会调用构造函数,而是在执行强制转换。例如:
是相同的:
1
| return static_cast<foo>(0); // or (foo)0 |
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| struct foo {
foo(int x) {
std::cout <<"ctor\
";
}
foo(const foo& x) {
std::cout <<"copy ctor\
";
}
operator bool() {
return true;
}
};
int main(int, char**) {
if (foo x = foo(1)) { /* */ }
} |
打印" ctor"。由于复制省略,不调用复制构造函数。
-
那不是解决方案,因为它不能回答问题。
-
在对象的上下文中,b仍不会默认构造然后分配给吗?这比if (bool b = true) { * * }好吗?
-
@cdhowie:否,在这两种情况下b都将被复制构造。正如@GMan对您的问题的评论,=并非总是赋值的。
-
@Let_Me_Be:它确实回答了这个问题-复制初始化是您可以在这里做的最好的事情(对于C ++ 03)。
-
@ildjarn如果那是您的意思,它不会调用复制构造函数。
-
+1为代码示例。这不是一个直接的答案,但它的确突出了我的问题的一部分是基于错误的知识,因此以回旋的方式回答了我的问题。
-
@Pubby:复制省略可以防止调用复制构造函数,但是它仍然是复制初始化的,因此必须定义并且可以访问复制构造函数。
-
@Puddy:但是它可以调用拷贝构造函数,实现是合法的。 (如果使用的是gcc,请尝试-fno-elide-constructors。)
因为" ="被定义为带有两个参数的函数,并且-在执行其工作后-返回第一个参数的值。构造函数不返回值。
-
有趣的理论。但是,此=是初始化而不是赋值。
-
Dingus dingus = another_dingus不是表达式,没有任何值。
-
不必争论,但是C ++遵循C,并且C允许这样做,因为通常使用" ="。如果" ="通常不返回值,则将不允许使用该语法。为什么有些人觉得有必要做出明智的回应?
-
@John:因为在这种情况下,正确答案和错误答案之间的确切区别。 if条件必须是一个表达式,但是=是一个语句,而不是表达式。
-
@约翰:它不是一个聪明的伙伴,希望有一个事实真实的答案。
-
我在这里看不到任何智能警报响应。在C或C ++中,即使赋值和初始化都使用=符号,也存在区别。赋值返回一个值,而初始化不返回。也许我们误解了您想说的话。
-
@ildjarn:=可以是赋值表达式的有效部分。 if (c = get())有效且正在进行分配;它与问题中被询问的情况不同。重要的区别是要声明一个类型和一个新名称,而不是=本身。
-
@Charles:对,我的意思是=的副本初始化形式,而不是赋值形式。我应该更具体一些。
-
我引用了" C ++编程第三版"的p296中的内容:int c 1 = s [1]; // c1 = s.operator [](1).operator char()。这是作业还是初始化?
-
@sehe:但是初始化不是c1(s.operator [](1).operator char())吗?有点糊涂在那里从严格的意义上讲,我希望初始化会调用构造函数,而赋值会调用operator =(/ * ... * /)。
-
已解决:publib.boulder.ibm.com/infocenter/comphelp/v8v101/
-
@sehe:你应该放松伴侣。我已经发布了指定答案的链接。您的答案就是您的答案,它是非权威性的,也没有引用任何资料来证明您的答案是正确的,就我所知,您也不是权威的信息来源,也没有为您的答案辩护,这是一个简单的否定。
-
@sehe:对不起,队友,我再也看不到你的意思了。我不同意GMans的问题。我想知道这句话是否正确。我已经投票支持GMans解决方案。 Stroustrup编写评论的方式认为这可能是对operator =的调用,而不是对构造函数的调用。我澄清说这不是被引用的电话。我似乎在那儿碰到了一个情人,对不起……振作起来,这不是一场比赛。