使用const引用延长变量生命周期
使用const引用延长变量生命周期
我们都知道const 引用在一定情况下会延长临时变量的生命周期,下面我们看一下具体哪些情况能够延长以及需要注意的地方。本节课不考虑编译器优化。
编译环境
Visual Studio 2022 / v143 / C++20
const引用一个右值
临时构造函数
#include <iostream>
class A
{
public:
// 空参构造
A()
{
std::cout << "A()" << std::endl;
}
// 带参构造
A(const int v) : v(v)
{
std::cout << "A(const int v)" << std::endl;
}
// 拷贝构造
A(const A&)
{
std::cout << "const A& " << std::endl;
};
// (左值)拷贝赋值
A& operator=(const A&)
{
std::cout << "A& operator=(const A&) " << std::endl;
}
// 析构
~A()
{
std::cout << __FUNCTION__ << std::endl;
}
int v = 0;
};
class D
{
public:
D(const A& a) :_a(a) {}
~D()
{
std::cout << __FUNCTION__ << std::endl;
}
const A& _a;
};
int main()
{
A(); // 验证A()是临时变量,出作用域 分号 ; 即走完当前行便被析构
{
// 使用const引用延长它的生命
const A& a = A(); // 这是个右值,必须用右值引用或常引用
//A& a = A(); // 报错: 这是个右值,必须用右值引用或常引用
//A&& a = A(); // ok
//D d(a); // 延长了 c 的生命周期
std::cout << "A.v: " << a.v << std::endl;
}
std::cout << "main return" << std::endl;
return 0;
}
首先我们创建了一个临时变量A(),然后使用const引用延长他的生命周期。输出如下:
A() // 临时 1
A::~A
A() // 被引用的临时2
A.v: 0
A::~A
main return
函数参数
//A setA(A& a) // 非常量引用不能绑定右值
A setA(const A& a1)
{
std::cout << &a << std::endl;
return a1;
}
{
const A& a2 = setA(A(2));
std::cout << &a2 << std::endl;
} // 大括号
「输出」
A(const int v)
00AFFC88
const A&
A::~A // 函数结束,a1被析构
00AFFD90
A::~A // a2 析构
首先创建了一个临时的A(2)对象, 然后绑定到setA函数参数,A2的生命周期延长至函数体结束。在函数内将a拷贝一份到函数返回值,之后返回值被延长至大括号结束。
在vs中,此过程产生一次拷贝构造。
错误的使用-返回一个临时变量的引用
//A setA(A& a) // 非常量引用不能绑定右值
// 返回一个临时变量的引用, 未定义的行为
const A& setA(const A& a1)
{
std::cout << &a1 << std::endl;
return a1;
}
int main()
{
const A& a2 = setA(A(2));
std::cout << &a2 << std::endl;
return 0;
}
「可能的输出」
A(const int v)
0062FB6C
A::~A // 被析构
2
0062FB6C
上面结果大部分情况下运行正常,因为对象被析构不代表内存被清理,我们可以用调试器看一下。但是a2引用了一个已经被析构的对象,不属于标准中定义的行为,且不容易被发现。类似的情况还有如下:
int n = 1;
auto p = std::minmax(n, n+1);
int m = p.first; // ok
int x = p.second; // 未定义行为
// 注意结构化绑定有同样的问题
auto [mm, xx] = std::minmax(n, n+1);
xx; // 未定义行为
实战-一个简单的日志输出
我们利用临时变量出作用域被析构、以及使用const引用能够延长其生命周期来实现一个简单的日志输出类。
#include <iostream>
#include <source_location>
#include <string>
#include <format>
#include <ranges>
//
// main.cpp:12 message.
enum class LogLevel
{
info = 0,
error
};
// 抽象->
//class LogFormatter; // 输出的格式
//class LogAppender; // 输出的地方
class MyLogger
{
public:
MyLogger(LogLevel level, const std::source_location& localtion = std::source_location::current()):
_localtion(localtion),
_level(level)
{ }
// 析构的时候进行日志输出
~MyLogger()
{
std::cout << format() << std::endl;
}
const MyLogger& operator<<(const std::string& message) const
{
_message += message;
return *this;
}
private:
std::string format()
{
std::string strLevel = "unknown";
if (LogLevel::info == _level) {
strLevel = "Info";
}
else if (LogLevel::error == _level) {
strLevel = "Error";
}
return std::format("{} {}:{} {}", static_cast<int>(_level), _localtion.file_name(), _localtion.line(), _message);
}
private:
const std::source_location& _localtion;
mutable std::string _message;
LogLevel _level;
};
int main()
{
{
MyLogger(LogLevel::info) << "log message";
// 延长生命周期
const auto& logger = MyLogger(LogLevel::error);
// 继续写入上一个日志
for (auto i : std::views::iota(1, 10)) {
logger << std::to_string(i) << " ";
}
}
return 0;
}

浙公网安备 33010602011771号