C++ 析构函数通过引用
C++ Destructor thru reference
我想和你分享一个我没有解决的小问题,这是代码(仅供测试):
#include <windows.h>
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <type_traits>
#include <sstream>
struct Procedure {
Procedure(HANDLE)
{ std::cout << "ctor w/connection: " << this << std::endl; }
~Procedure()
{ std::cout << "dtor: " << this << std::endl; }
Procedure(Procedure &&rhs) {
std::cout << "ctor w/move: " << this << std::endl;
this->m_Params = std::move(rhs.m_Params);
}
Procedure& operator= (Procedure &&rhs) {
std::cout << "operator= w/move: " << this << std::endl;
if (this != &rhs) this->m_Params = std::move(rhs.m_Params);
return *this;
}
Procedure& AppendParam(const std::string &str) {
std::cout << "appendparam: " << this << std::endl;
m_Params.push_back(str);
return *this;
}
void Execute( const std::string &str) {
std::stringstream ss;
ss << str << '(';
for (int i = 0, mx = m_Params.size(); i < mx; ++i) {
ss << '\'' << m_Params[i] << '\'';
if (i < mx - 1) ss << ',';
}
ss << ");";
std::cout << "calling: " << this << " : " << ss.str() << std::endl;
}
private:
Procedure(const Procedure &) = delete;
Procedure& operator=(const Procedure &) = delete;
std::vector<std::string> m_Params;
};
Procedure ProcedureCaller()
{ return Procedure(nullptr); }
int __cdecl main() {
std::cout << "test1---------------------" << std::endl; {
auto &proc = ProcedureCaller().AppendParam("param_1").AppendParam("param_2");
proc.Execute("sp_test");
}
std::cout << "test2--------------------" << std::endl; {
auto proc = ProcedureCaller();
proc.AppendParam("param_A").AppendParam("param_B");
proc.Execute("sp_test_2");
}
std::cout << "test3--------------------" << std::endl; {
ProcedureCaller().AppendParam("param_AA").AppendParam("param_BB").Execute("sp_test_2");
}
return 0;
}
这是我得到的结果:
test1---------------------
ctor w/connection: 00F8FC98
appendparam: 00F8FC98
appendparam: 00F8FC98
dtor: 00F8FC98
calling: 00F8FC98 : sp_test();
test2--------------------
ctor w/connection: 00F8FD70
appendparam: 00F8FD70
appendparam: 00F8FD70
calling: 00F8FD70 : sp_test_2('param_A','param_B');
dtor: 00F8FD70
test3--------------------
ctor w/connection: 004FFB20
appendparam: 004FFB20
appendparam: 004FFB20
calling: 004FFB20 : sp_test_2('param_AA','param_BB');
dtor: 004FFB20
我有几个问题:
1- 为什么 "test1" 的 dtor 在其范围结束之前被调用?我的意思是,代码甚至没有调用 Execute
方法。
2- 如果 "test1" 的 dtor 是临时对象,为什么我没有看到来自 move ctor
的日志,或者至少是编译器错误,因为它试图使用已删除的 copy ctor
?
3- "test1" 和 "test2" 之间有什么区别,我希望能够以任何我想要的方式调用 Execute
。
4- 我错过了什么?
谢谢。
这里有一个更简单的版本来演示同样的问题:
struct X {
X() = default;
~X() { std::cout << "dtor\n"; }
X& self() { return *this; }
};
int main()
{
X& x = X().self();
std::cout << "here?\n";
}
该程序在打印 here
之前打印 dtor
。为什么?问题是,我们有一个没有延长生命周期的临时 (X()
),因此它在包含它的表达式末尾被销毁(即 X().self()
)。当您获得对它的引用时,它并不是生命周期延长的神奇引用之一 - 您得到的只是对立即超出范围的对象的引用。
只有在非常有限的情况下才会延长生命周期。临时文件必须立即绑定到一个引用,这只能发生在 const
个引用中:
X const& x = X();
std::cout << "here\n";
现在这会在 dtor
之前打印 here
。
此外,没有可传递的生命周期延长。即使在最初的例子中我们做了:
X const& x = X().self();
我们仍然会得到悬空引用。
- 在"test1"情况下,
proc
引用的对象是临时的。它在创建它的完整表达式末尾超出范围,并且 proc
立即悬空。请注意,没有生命周期延长有两个原因:a) 生命周期延长只发生在 const
lvalue-references 和 rvalue-references 和 b) 生命周期延长只发生在纯右值,而 [= 返回的引用13=] 是一个左值。
proc
在 "test1" 情况下是引用,而不是对象。没有移动或复制发生,因为没有要移动或复制到的对象。引用绑定到 ProcedureCaller
返回的临时对象,它在下一个 ;
. 超出范围
- "test1"和"test2"的区别在于
proc
在"test1"中是引用,在"test2"中是实际对象。如果您试图在 "test2" 情况下使 proc
成为引用,编译器会报错。只有 const
lvalue-references 和 rvalue-references 可以绑定到纯右值(例如从函数返回的对象)。它在 "test1" 情况下起作用的唯一原因是您通过 returns 和 lvalue-reference. 的方法获得了 "laundered" 纯右值
我想和你分享一个我没有解决的小问题,这是代码(仅供测试):
#include <windows.h>
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <type_traits>
#include <sstream>
struct Procedure {
Procedure(HANDLE)
{ std::cout << "ctor w/connection: " << this << std::endl; }
~Procedure()
{ std::cout << "dtor: " << this << std::endl; }
Procedure(Procedure &&rhs) {
std::cout << "ctor w/move: " << this << std::endl;
this->m_Params = std::move(rhs.m_Params);
}
Procedure& operator= (Procedure &&rhs) {
std::cout << "operator= w/move: " << this << std::endl;
if (this != &rhs) this->m_Params = std::move(rhs.m_Params);
return *this;
}
Procedure& AppendParam(const std::string &str) {
std::cout << "appendparam: " << this << std::endl;
m_Params.push_back(str);
return *this;
}
void Execute( const std::string &str) {
std::stringstream ss;
ss << str << '(';
for (int i = 0, mx = m_Params.size(); i < mx; ++i) {
ss << '\'' << m_Params[i] << '\'';
if (i < mx - 1) ss << ',';
}
ss << ");";
std::cout << "calling: " << this << " : " << ss.str() << std::endl;
}
private:
Procedure(const Procedure &) = delete;
Procedure& operator=(const Procedure &) = delete;
std::vector<std::string> m_Params;
};
Procedure ProcedureCaller()
{ return Procedure(nullptr); }
int __cdecl main() {
std::cout << "test1---------------------" << std::endl; {
auto &proc = ProcedureCaller().AppendParam("param_1").AppendParam("param_2");
proc.Execute("sp_test");
}
std::cout << "test2--------------------" << std::endl; {
auto proc = ProcedureCaller();
proc.AppendParam("param_A").AppendParam("param_B");
proc.Execute("sp_test_2");
}
std::cout << "test3--------------------" << std::endl; {
ProcedureCaller().AppendParam("param_AA").AppendParam("param_BB").Execute("sp_test_2");
}
return 0;
}
这是我得到的结果:
test1---------------------
ctor w/connection: 00F8FC98
appendparam: 00F8FC98
appendparam: 00F8FC98
dtor: 00F8FC98
calling: 00F8FC98 : sp_test();
test2--------------------
ctor w/connection: 00F8FD70
appendparam: 00F8FD70
appendparam: 00F8FD70
calling: 00F8FD70 : sp_test_2('param_A','param_B');
dtor: 00F8FD70
test3--------------------
ctor w/connection: 004FFB20
appendparam: 004FFB20
appendparam: 004FFB20
calling: 004FFB20 : sp_test_2('param_AA','param_BB');
dtor: 004FFB20
我有几个问题:
1- 为什么 "test1" 的 dtor 在其范围结束之前被调用?我的意思是,代码甚至没有调用 Execute
方法。
2- 如果 "test1" 的 dtor 是临时对象,为什么我没有看到来自 move ctor
的日志,或者至少是编译器错误,因为它试图使用已删除的 copy ctor
?
3- "test1" 和 "test2" 之间有什么区别,我希望能够以任何我想要的方式调用 Execute
。
4- 我错过了什么?
谢谢。
这里有一个更简单的版本来演示同样的问题:
struct X {
X() = default;
~X() { std::cout << "dtor\n"; }
X& self() { return *this; }
};
int main()
{
X& x = X().self();
std::cout << "here?\n";
}
该程序在打印 here
之前打印 dtor
。为什么?问题是,我们有一个没有延长生命周期的临时 (X()
),因此它在包含它的表达式末尾被销毁(即 X().self()
)。当您获得对它的引用时,它并不是生命周期延长的神奇引用之一 - 您得到的只是对立即超出范围的对象的引用。
只有在非常有限的情况下才会延长生命周期。临时文件必须立即绑定到一个引用,这只能发生在 const
个引用中:
X const& x = X();
std::cout << "here\n";
现在这会在 dtor
之前打印 here
。
此外,没有可传递的生命周期延长。即使在最初的例子中我们做了:
X const& x = X().self();
我们仍然会得到悬空引用。
- 在"test1"情况下,
proc
引用的对象是临时的。它在创建它的完整表达式末尾超出范围,并且proc
立即悬空。请注意,没有生命周期延长有两个原因:a) 生命周期延长只发生在const
lvalue-references 和 rvalue-references 和 b) 生命周期延长只发生在纯右值,而 [= 返回的引用13=] 是一个左值。 proc
在 "test1" 情况下是引用,而不是对象。没有移动或复制发生,因为没有要移动或复制到的对象。引用绑定到ProcedureCaller
返回的临时对象,它在下一个;
. 超出范围
- "test1"和"test2"的区别在于
proc
在"test1"中是引用,在"test2"中是实际对象。如果您试图在 "test2" 情况下使proc
成为引用,编译器会报错。只有const
lvalue-references 和 rvalue-references 可以绑定到纯右值(例如从函数返回的对象)。它在 "test1" 情况下起作用的唯一原因是您通过 returns 和 lvalue-reference. 的方法获得了 "laundered" 纯右值