我最近注意到C++ 0x中的一个类需要一个显式的默认构造函数.但是,我没有想出一个可以隐式调用默认构造函数的场景.这似乎是一个相当无意义的说明者.我想也许它会Class c;
不赞成,Class c = Class();
但似乎并非如此.
来自C++ 0x FCD的一些相关引用,因为我更容易导航[类似文本存在于C++ 03中,如果不在同一个地方]
12.3.1.3 [class.conv.ctor]
默认构造函数可以是显式构造函数; 这样的构造函数将用于执行默认初始化或值初始化(8.5).
它继续提供显式默认构造函数的示例,但它只是模仿我上面提供的示例.
8.5.6 [decl.init]
默认初始化T类型的对象意味着:
- 如果T是一个(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的);
8.5.7 [decl.init]
对值类型T的对象进行值初始化意味着:
- 如果T是具有用户提供的构造函数(12.1)的(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的);
在这两种情况下,标准都要求调用默认构造函数.但是如果默认构造函数是非显式的,那就会发生这种情况.为了完整起见:
8.5.11 [decl.init]
如果没有为对象指定初始化程序,则默认初始化该对象;
据我所知,这只是从没有数据的转换.这没有意义.我能想到的最好的是以下内容:
void function(Class c);
int main() {
function(); //implicitly convert from no parameter to a single parameter
}
Run Code Online (Sandbox Code Playgroud)
但显然这不是C++处理默认参数的方式.还有什么会使explicit Class();
行为与众不同Class();
?
生成此问题的具体示例是std::function
[20.8.14.2 func.wrap.func].它需要几个转换构造函数,其中没有一个被标记为显式,但默认构造函数是.
我并不完全清楚新extern template
功能如何在C++ 11中运行.据我所知,它旨在帮助加快编译时间,并简化与共享库的链接问题.这是否意味着编译器甚至不解析函数体,强制进行非内联调用?或者它是否只是指示编译器在进行非内联调用时不生成实际的方法体?显然,链接时代码生成无法承受.
作为差异可能重要的具体示例,请考虑对不完整类型进行操作的函数.
//Common header
template<typename T>
void DeleteMe(T* t) {
delete t;
}
struct Incomplete;
extern template void DeleteMe(Incomplete*);
//Implementation file 1
#include common_header
struct Incomplete { };
template void DeleteMe(Incomplete*);
//Implementation file 2
#include common_header
int main() {
Incomplete* p = factory_function_not_shown();
DeleteMe(p);
}
Run Code Online (Sandbox Code Playgroud)
在"实现文件2"中,delete
指向的指针是不安全的Incomplete
.所以内联版本DeleteMe
会失败.但是如果它被保留为实际的函数调用,并且函数本身是在"实现文件1"中生成的,那么一切都将正常工作.
作为推论,具有类似extern template class
声明的模板化类的成员函数的规则是否相同?
出于实验目的,MSVC会为上述代码生成正确的输出,但如果extern
删除该行,则会生成有关删除不完整类型的警告.然而,这是他们多年前推出的非标准扩展的残余,所以我不确定我能相信这种行为有多少.我无法访问任何其他构建环境来进行实验[save ideone et al,但仅限于一个翻译单元在这种情况下是相当有限的].
这可能是一个非常简单的解释,但是如果我错了,我会尽可能多地给出背景故事.对于如此冗长而道歉.我正在使用gcc4.5,并且我意识到c ++ 0x支持仍然有点实验性,但我将假设我所看到的行为存在非bug相关的原因.
我正在尝试使用可变参数函数模板.最终目标是建立一个缺点std::pair
.它不是一个自定义类型,只是一对配对对象.构造列表的函数必须以某种方式递归,最终返回值取决于递归调用的结果.作为附加扭曲,连续参数在插入列表之前被添加在一起.所以如果我通过[1,2,3,4,5,6],最终结果应该是{1 + 2,{3 + 4,5 + 6}}.
我最初的尝试相当天真.一个函数,Build,有两个重载.一个人拿了两个相同的参数,然后简单地返回它们 另一个采用了两个参数和一个参数包.返回值是一对由两个设置参数和递归调用之和组成.回想起来,这显然是一个有缺陷的策略,因为当我试图找出它的返回类型时,函数没有被声明,所以它别无选择,只能解析为非递归版本.
我明白了 我困惑的地方是第二次迭代.我决定让这些函数成为模板类的静态成员.函数调用本身不是参数化的,而是整个类.我的假设是,当递归函数尝试生成其返回类型时,它将使用自己的静态函数实例化整个新版本的结构,并且所有内容都可以自行运行.
结果是:"错误:没有匹配函数来调用BuildStruct<double, double, char, char>::Go(const char&, const char&)
"
违规代码:
static auto Go(const Type& t0, const Type& t1, const Types&... rest)
-> std::pair<Type, decltype(BuildStruct<Types...>::Go(rest...))>
Run Code Online (Sandbox Code Playgroud)
我的困惑来自于这样的事实:参数BuildStruct
应始终与发送的参数类型相同BuildStruct::Go
,但在错误代码Go中缺少最初的两个双参数.我在这里错过了什么?如果我最初关于如何选择静态函数的假设是不正确的,为什么它试图调用错误的函数而不是根本找不到函数?它似乎只是混合类型,而且我无法想出为什么.如果我在初始调用中添加其他参数,它总是在失败之前向下挖掘到最后一步,因此假设递归本身至少部分工作.这与初始尝试形成鲜明对比,初始尝试始终无法立即找到函数调用.
最终,我已经解决了这个问题,一个相当优雅的解决方案,几乎不像前两次尝试.所以我知道如何做我想做的事.我正在寻找我所看到的失败的解释.
完整的代码,因为我确信我的口头描述不足.首先是一些样板文件,如果您觉得有必要执行代码并亲自查看它.然后是合理失败的初始尝试,然后是第二次尝试,但没有.
#include <iostream>
using std::cout;
using std::endl;
#include <utility>
template<typename T1, typename T2>
std::ostream& operator <<(std::ostream& str, const std::pair<T1, T2>& p) {
return str << "[" << p.first << ", " << p.second << …
Run Code Online (Sandbox Code Playgroud) 在研究最近的一个问题时,我发现了'03标准[1]中的以下条款:
当typeid应用于类型为多态类类型(10.3)的左值表达式时,结果引用一个type_info对象,表示左值引用的最派生对象(1.8)的类型(即动态类型) .如果通过将一元*运算符应用于指针并且指针是空指针值(4.10)来获得左值表达式,则typeid表达式将抛出bad_typeid异常(18.5.3).
具体来说,我想知道最后一位,它为解除引用空指针的结果提供了良好定义的行为.据我所知,这是唯一一次这样做[2].具体来说,dynamic_cast<T&>
对于这种情况没有特殊处理,这似乎是一个更有用的场景.双重考虑dynamic_cast<T&>
已经被定义为在某些情况下抛出异常.
是否有特殊原因使这种特殊表达得到特殊处理?它似乎是完全随意的,所以我猜他们有一些特定的用例.
[1]类似的子句存在于'11中,但它指的是glvalue表达式,而不是左值表达式.
[2] delete 0;
并且dynamic_cast<T*>(0)
接近,但在这两种情况下,您都在处理指针值,而不是实际对象.
我注意到当抛出类型可移动时,MSVC和g ++如何处理临时异常对象的创建有轻微的差异.狩猎这些下来提出了额外的问题.
在进一步讨论之前,这是我的问题的核心:在没有复制/移动省略的情况下,谁做了标准说明应该如何创建临时异常对象?目前,我能做的最好的是以下引用,从15.1/3开始:
throw-expression初始化一个临时对象,称为异常对象,其类型是通过从throw的操作数的静态类型中删除任何顶级cv限定符并从"T的数组"调整类型来确定的.函数返回T"到"指向T"的指针或"指向函数返回T的指针".
我猜测答案被隐藏在其他地方的语言中,它定义了表达式的类型以及如何初始化对象,但我没有运气拼凑它们.当抛出一个对象时,异常对象是否得到(a)构造的复制,(b)在适当的情况下移动构造,否则复制构造,或(c)以实现定义的方式初始化?
请考虑以下代码:
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
struct Blob {
Blob() { cout << "C" << endl; }
Blob(const Blob&) { cout << "c" << endl; }
Blob(Blob&&) { cout << "m" << endl; }
Blob& operator =(const Blob&) { cout << "=" << endl; return *this; }
Blob& operator =(Blob&&) { cout << "=m" << endl; return *this; }
~Blob() { cout << "~" << endl; }
int i;
};
int main() …
Run Code Online (Sandbox Code Playgroud) 第一:在哪里std::move
和std::forward
界定?我知道他们做了什么,但我找不到任何标准标题包含它们的证据.在gcc44中,有时std::move
可用,有时则不可用,因此明确的include指令会很有用.
在实现移动语义时,源可能处于未定义状态.该状态是否必须是对象的有效状态?显然,您需要能够调用对象的析构函数,并能够通过类暴露的任何方式为其分配.但其他操作是否有效?我想我要问的是,如果你的班级保证某些不变量,当用户说他们不再关心它们时你是否应该努力强制执行这些不变量?
下一篇:当你不关心移动语义时,是否有任何限制会导致非const引用在处理函数参数时优于rvalue引用? void function(T&);
在void function(T&&);
从呼叫者的角度来看,能够传递功能的临时值是偶尔有用,所以它好像应该授予该选项时,它是可行的,这样做的.并且rvalue引用本身就是lvalues,因此你不能无意中调用move-constructor而不是copy-constructor,或类似的东西.我没有看到一个缺点,但我确信有一个.
这让我想到了最后一个问题.您仍然无法将临时对象绑定到非const引用.但是您可以将它们绑定到非const右值引用.然后,您可以将该引用作为另一个函数中的非const引用传递.
void function1(int& r) { r++; }
void function2(int&& r) { function1(r); }
int main() {
function1(5); //bad
function2(5); //good
}
Run Code Online (Sandbox Code Playgroud)
除了它没有做任何事情之外,该代码有什么问题吗?我的直觉当然不是,因为改变右值参考是他们存在的一个重点.如果传递的值是合法的const,编译器将捕获它并对你大喊大叫.但从各方面来看,这是一个机制的一个周期,可能是因为某种原因而存在,所以我只是想确认我没有做任何愚蠢的事情.
我只是想知道,如何通过c ++程序管理类型信息?
例如.漂浮f; 需要32位内存.但由于所有位都用于保存值,程序如何记住它是float类型?
(我知道这是一个非常古怪的问题...)
请考虑以下代码:
#include <vector>
int sum(const std::vector<int>& v) {
int count = 0;
for(int i = 0; i < v.size(); ++i)
count += v[i];
return count;
}
Run Code Online (Sandbox Code Playgroud)
出于这个问题的目的,假设这是整个翻译单元,编译器的实现std::vector::size
和std::vector::operator []
可用.
编译器可以简单地告诉它v
在循环中没有被修改,因为除了源代码之外没有函数调用.因此,编译器size()
在循环外提升调用是完全合理的:
for(int i = 0, size = v.size(); i < size; ++i)
Run Code Online (Sandbox Code Playgroud)
但是,在最近的一个答案中,有人建议,由于v
可能被另一个线程修改,因此不允许编译器执行该优化.然而,在我看来,在这种情况下,函数已经被遗忘了,因此限制编译器的选项是没有意义的.但我不明白新标准关于排序的规则,以及它们如何与表达式重新排序相互作用,所以也许我的直觉是错误的.
我愿意假设在C++ 03中,这种转换总是有效的.但是C++ 11呢?显然,我可以将函数抛给编译器,看看会发生什么,但这是检查一致性的糟糕方法.
由于 sizeof 运算符评估操作数是否为 VLA,因此我尝试将其测试为:
#include<stdio.h>
int main(void)
{
int sz=20,i=0,j=0;
int arr[sz];
printf("%d\n",sizeof((++i,sz)));
printf("%d\n",sizeof((++j,arr)));
printf("%d\n%d\n",i,j);
}
Run Code Online (Sandbox Code Playgroud)
我认为我不会增加,因为 sz 不是 VLA,但 j 会增加,因为 arr 是 VLA。
但是在输出中, i 和 j 都没有增加。