我偶然发现了以下代码片段:
#include <iostream>
#include <string>
using namespace std;
class First
{
string *s;
public:
First() { s = new string("Text");}
~First() { delete s;}
void Print(){ cout<<*s;}
};
int main()
{
First FirstObject;
FirstObject.Print();
FirstObject.~First();
}
Run Code Online (Sandbox Code Playgroud)
该文本表示此代码段应该导致运行时错误.现在,我对此并不十分肯定,所以我尝试编译并运行它.有效.奇怪的是,尽管所涉及的数据非常简单,但在打印"文本"之后程序结结巴巴,并且仅在一秒钟之后完成.
我添加了一个要打印到析构函数的字符串,因为我不确定显式调用这样的析构函数是否合法.程序打印两次字符串.所以我的猜测是析构函数被调用两次,因为正常的程序终止不知道显式调用并试图再次销毁对象.
一个简单的搜索确认显式调用自动化对象上的析构函数是危险的,因为第二次调用(当对象超出范围时)具有未定义的行为.所以我很幸运,我的编译器(VS 2017)或这个特定的程序.
关于运行时错误,文本是否完全错误?或者运行时错误真的很常见吗?或者也许我的编译器实现了某种针对这类事情的warding机制?
c++ destructor lifetime undefined-behavior explicit-destructor-call
在阅读有关不同主题的同时,我遇到了一种奇怪的行为,至少对我而言.整个思想起源于auto和括号之间的特殊相互作用.如果你写的东西如下:
auto A = { 1, 2, 3 }
Run Code Online (Sandbox Code Playgroud)
编译器将推断A为a std::initializer_list.奇怪的是,类似的规则不仅适用于auto可能有特殊原因的地方,也适用于其他事情.如果你写下面的内容:
template<typename T>
void f(std::vector<T> Vector)
{
// do something
}
Run Code Online (Sandbox Code Playgroud)
你当然不能这样称呼它:
f({ 1, 2, 3});
Run Code Online (Sandbox Code Playgroud)
即使a std::vector可以被支撑初始化.但是,如果替换std::vectorwith std::initializer_list,则调用有效,编译器将正确推断int为类型T.然而,更有趣的是,在前一种情况下你需要#include <vector>,在后者你不需要#include <initializer_list>.这让我想到并且在测试之后我意识到某种程度上std::initializer_list不需要它自己的标题,所以它在某种程度上是"基础"功能的一部分.
而且,对于所有有意义的事情,std::initializer_list应该以与lambdas相同的方式对标准对象进行可调用对象(在最严格的意义上,即具有a的对象operator()).换句话说,未命名的支撑定义应默认为std::initializer_listlambdas(大多数)是未命名的可调用对象.
这个推理是否正确?此外,这种行为可以改变,如果是这样,怎么样?
更新:initializer_list发现的标题是传递的iostream(非常奇怪).但问题仍然存在:为什么呼叫适用std::initializer_list而不适用std::vector?
出于学习目的,我编写了以下代码:
void Swap(const int *&Pointer1, const int *&Pointer2)
{
const int *Tmp = Pointer2;
Pointer2 = Pointer1;
Pointer1 = Tmp;
}
Run Code Online (Sandbox Code Playgroud)
我对这段代码有一些疑问,以及在这种情况下顶级/低级constness如何工作,有3个或更多"级别".
在阅读有关不同类型的初始化,我偶然发现的众多怪异的交互的一个std::initializer_list(以前的帖子在这里).这显然是一个简单的主题,是C++书籍中首次出现的主题之一std::vector.
关键是,如果你有一个构造函数接受(我猜只有)std::initializer_list,当你对大小写进行初始化时,这个构造函数将是强烈优先的:换句话说,std::vector<int> NewVect{5, 10}将创建一个包含5和10的对象,而不是5元素初始化为10(std::vector<int> NewVect(5, 10)) .
auto大括号初始化auto A={1,2,3}会发生特殊行为(将被推断为a std::initializer_list<int>).现在,我无法相信C++对特定对象的特定处理,因为据我所知,标题只是编写良好且有用的代码片段.此外,如果你没有直接或间接地,那些语法不起作用#include <initializer_list>,即使我的编译器VS2017打印了一组非常特殊的错误,指出它需要该标头才能工作(你可以轻松地测试它auto).
所以,我的问题是,理所当然地认为这种行为是来自initiliazer列表标题的代码的影响,而我的编译器可能是为了假设使用STD而构建的,它是如何实现的?我是否可以将此行为重新路由,除了显式调用之外永远不会发生(也就是说,std::vector<int> NewVect{5, 10}相当于std::vector<int> NewVect(5, 10)你现在需要调用std::vector<int> NewVect{std::initializer_list<int>{5, 10}})?是否可以将此行为提供给其他用户构建的类?
我正在研究顺序容器库,有些东西让我思考.
我理所当然地认为除了特殊情况之外的每个容器,例如forward_list,它在一个方向上是不可能的,完全支持迭代器算术.相反,我发现通用容器只支持一组非常具体的操作:*和 - >(由于显而易见的原因),前后增量和减量(不是100%确定后期)和相等/不等式关系运算符.为vector,string和deque保留了更复杂的算法.
我不明白这个约束.我们被禁止的原因是什么,例如,在两个迭代器之间减去或者在迭代器中添加一个int?毕竟,如果我们可以访问前/后递增/递减,我们可以很容易地通过使用整数计数器重复迭代这些操作来实现加法和减法.
我正在重新学习C++,我发现自己经常写这样的代码片段:
vector<string> Text;
string Line;
while (getline(cin, Line))
{
Text.push_back(Line);
}
Run Code Online (Sandbox Code Playgroud)
我想知道是否有一种更紧凑的方式来编写循环只使用基本功能(例如,没有用户编写的功能) - 或多或少,把所有东西都放在条件中?
我在学习模板时遇到过以下练习:
#include <iostream>
using namespace std;
template <class T>
class A {
T _v;
public:
A() {}
A(T v) : _v(v) {}
friend ostream & operator<<(ostream & c, const A<T> & v);
};
template <class T>
ostream & operator<<(ostream & c, const A<T> & v){
c << v._v; return c;
}
int main()
{
A<int>a(10);
cout << a << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
该代码片段应该在编译期间产生错误,而且确实如此。这是链接器错误,但我无法理解。
我尝试更改几行代码,错误似乎是由模板的安装引起的operator<<,因为删除该模板并编写特定的运算符使代码可以工作。我还感觉模板会被实例化多次,而不仅仅是int.
然而,据我有限的知识,模板定义似乎很好。我缺少什么?
确切的错误(VS 2017)是: Error LNK2019: unresolved external symbol "class std::basic_ostream > …
c++ ×7
templates ×2
c++11 ×1
const ×1
containers ×1
destructor ×1
iterator ×1
lifetime ×1
pointers ×1
reference ×1