参考
这是 Back to the Basics: Essentials of Modern C++ 的视频总结。
要点
1. 使用 range 进行迭代
for (auto& e : c) {...}
没有特别需要说明的。
2. 避免使用 new 和 delete
在需要使用 new 创建对象的场合,使用 unique_ptr 代替。例如:
#include
#include
#include
namespace c14 {
template
std::unique_ptr make_unique(Args &&... args) {
return std::unique_ptr(new T(std::forward(args)...));
}
} // namespace c14
class Person {
public:
explicit Person(const std::string &name, int age) {
name_ = name;
age_ = age;
std::cout << "Created with lvalue" << std::endl;
}
explicit Person(const std::string &&name, int age) {
name_ = std::move(name);
age_ = age;
std::cout << "Created with rvalue" << std::endl;
}
void speak() {
std::cout << "Hello, I'm " << name_ << ", " << age_ << " years old."
<< std::endl;
}
~Person() { std::cout << name_ << " deleted..." << std::endl; }
private:
std::string name_;
int age_;
};
int main(int argc, char const *argv[]) {
auto p = c14::make_unique("yy", 27);
p->speak();
return 0;
}
/* 输出:
Created with rvalue
Hello, I'm yy, 27 years old.
yy deleted...
*/
尽量使用 unique_ptr 来管理对象的所有权,当确定某个对象需要共享则使用 shared_ptr。
- 它的开销和裸指针一样
- 它是 exception-safe 的,即使发生异常也能顺利析构
- 无需进行麻烦的引用计数
以上例子有两个值得注意的地方。
- std::make_unique 是 C++14 引入的特性。但是我们这里为了方便 C++11 用户自己手动实现了一个。实际也不复杂,主要是变长参数模板的处理,以及 std::forward 实现的完美参数转发。相比 std::move, std::forward 会进行引用折叠。
- 区分左值与右值。
3. 在传值的时候仍然应该使用 * 以及 &
这是最安全而且性能最好的。
不要使用智能指针作参数,无论是 by value 还是 by reference,除非你想在函数里控制对象的生命周期。
无论是 copy 还是 assign 一个 shared_ptr,都会影响引用计数,改变对象的生命周期。
比较理想的做法应该是传递引用或裸指针。
例如:
auto upw = make_unique();
...
f( *upw );
auto spw = make_shared();
...
g( spw.get() );
auto 关键字
主要讲两点。
- 关于性能
auto x = value;
这个语句是否创建了一个 value 然后通过 copy/move 的方式给 x 呢?
并没有。实际上以下两句是等同的。
T x = a; // 1
T x(a); // 2
那么形如:
auto x = type{value};
这个语句是否创建了一个临时对象并且通过 copy/move 转移给 x 呢?
答案是肯定的,但是编译器可能会优化。并且仍然需要保证这个临时对象是 copyable/movable 的。
- 关于不能使用 auto 的地方
在使用上面第二种方式初始化时,auto 不适用于无法 copy/move 或者 copy/move 代价昂贵的对象。
auto lg = lock_guard{mu}; // error, not movable
auto ai = atomic{0}; // error, not movable
auto a = array{}; // compiles, but needlessly expensive
4. 右值优化
在参数传递中,可以恰当地使用右值优化。
#include
#include
class Employee {
public:
// 1
void set_name(const std::string& name) {
name_ = name;
std::cout << "set name with lvalue" << std::endl;
}
// 2
void set_name(std::string&& name) noexcept {
name_ = std::move(name);
std::cout << "set name with rvalue" << std::endl;
}
void speak() {std::cout<< "My name is " << name_ << std::endl;}
private:
std::string name_;
};
int main(int argc, char const *argv[]) {
Employee e;
std::string s = "ssss";
std::string b = "bbbb";
e.set_name(s);
e.speak();
e.set_name(s+b);
e.speak();
return 0;
}
值得注意的是 noexcept 这个关键字。由于函数 1 有可能会发生内存分配(例如传递一个右值作为参数),因此是可能发生诸如内存不足等异常的。而函数 2 是不可能的。这种设计体现了 exception-safety。
但是,对于构造函数,建议使用传值的方式。原因可以参考 stackoverflow。例子如下,注意是使用了 std::move。
class Employee {
public:
Employee() {}
// for constructor, pass by value is a good idea
explicit Employee(std::string name): name_(std::move(name)) {
}
private:
std::string name_;
5. 正确使用 &&
&& 不仅表示 rvalue reference,还可以表示 forwarding reference。
两者的使用场景不同。
- rvalue reference 用于右值优化,例如上面的:
// 1
void set_name(const std::string& name) {
name_ = name;
std::cout << "set name with lvalue" << std::endl;
}
// 2
void set_name(std::string&& name) noexcept {
name_ = std::move(name);
std::cout << "set name with rvalue" << std::endl;
}
- forwarding reference 用于编写 forwarder,可以在保留参数性质(左值、右值、const)的情况下传递参数。
namespace c14 {
template
std::unique_ptr make_unique(Args &&... args) {
return std::unique_ptr(new T(std::forward(args)...));
}
6. 使用 tuple 返回多个值
这里介绍了三种方式,个人推荐第三种,更清晰一点。
#include
#include
#include
using namespace std;
int main(int argc, char const *argv[]) {
set aSet{"Hello", "World", "SB"};
string str1 = "Hello";
string str2 = "You";
string str3 = "Me";
// C98
pair::iterator, bool> result1 = aSet.insert(str1);
if (result1.second == true) {
cout << "Inserted: " << *result1.first << endl;
} else {
cout << "Insert " << str1 << " failed." << endl;
}
// C11: auto
auto result2 = aSet.insert(str2);
if (result2.second == true) {
cout << "Inserted: " << *result2.first << endl;
}
// C11: tie
set::iterator iter;
bool success;
tie(iter, success) = aSet.insert(str3);
if (success == true) {
cout << "Inserted: " << *iter << endl;
}
return 0;
}