假设一个功能模板:
template <class T>
void foo(T /* dummy */) {...}
假设foo
被调用如下:
foo(Widget());
在这种情况下是否会构建Widget
对象?
This post询问有关未使用参数的类似问题(虚拟参数的参数肯定未使用)。回复表明,除非通过函数指针调用函数,否则编译器将优化未使用的参数。
但是,请考虑Alexandrescu的 Modern C ++ 第2.5节中的以下内容:
现在说你的应用程序中有一条规则:Widget类型的对象是不可触及的遗留代码,在构造时必须带两个参数,第二个是固定值,如-1。你自己的课程,来自Widget,没有这个问题。
...
在没有部分专业化功能的情况下,唯一可用的工具是重载。解决方案是传递类型为T的虚拟对象并依赖于重载:
template <class T, class U> T* Create(const U& arg, T /* dummy */) { return new T(arg); } template <class U> Widget* Create(const U& arg, Widget /* dummy */) { return new Widget(arg, -1); }
这样的解决方案会产生构建一个未使用的任意复杂对象的开销。
这表明编译器不够智能,无法避免构造伪参数的参数......
那么,哪个是正确的?如果Alexandrescu是正确的,那么为什么不进行这种优化呢?
答案 0 :(得分:15)
创建对象可能会产生副作用。
除非编译器能够证明没有副作用发生,或者标准的任何部分没有强制要求发生副作用,否则不允许在as下创建一个对象。 -if(编译器可以对你的代码做任何事情,只要它的行为 - 如果他们不做更改,达到标准的要求)或elision(你在某些情况下合并某些对象的生命周期,甚至如果它不表现为 - 如果你没有合并它们)规则。
作为一个例子,假设Widgets在中心位置注册了它们的存在。当对象被创建时,存在的小部件的数量将增加1 - 根据标准,这不会发生是非法的。
即使没有副作用,证明也没有副作用,需要编译器收集创建Widget所涉及的所有代码,并分析它“最后什么都不做” 。这可能会有所不同(大量代码的链接时优化,特殊的“对象将在时间Y消失”,以确定是否有任何副作用),不可能(我们正在谈论分析非图灵完整计算结果的平凡属性)。
所有这一切都是针对一个相对奇怪的角落案例,其中“有人无缘无故地创建了一个对象,然后在不使用它的情况下将其丢弃”。
答案 1 :(得分:11)
这不是“聪明”;这是关于正确的。标准中没有规则可以省略这种结构。如果构造函数没有副作用,那么只能通过 as-if 规则来避免它。
更一般地说,呼叫站点的代码并不总是能够知道定义站点上的参数是否为“虚拟”。否则,语言可能的指定方式不同,尽管这纯粹是猜测。
我们以std::string
为例,仅仅为了论证(lol):
// (N.B. no variable name; therefore "dummy")
void foo(std::string)
{}
#include <string>
void foo(std::string argument);
int main()
{
foo("will this be used to construct a std::string?");
}
是的,因为就TU2而言,它必须是。
在相反的例子中,忽略施工显然是一个可怕的错误:
#include <string>
#include <iostream>
void foo(std::string argument)
{
std::cout << argument << std::endl;
}
#include <string>
// (N.B. no variable name; therefore "dummy")
void foo(std::string);
int main()
{
foo("will this be used to construct a std::string?");
}
不(通过你的“建议”),造成正确的混乱。
根据C ++编译模型的性质,这两种情况都是100%无法检测到的。