參考
這是 Back to the Basics: Essentials of Modern C++ 的視頻總結。
要點
1. 使用 range 進行迭代
for (auto& e : c) {...}
沒有特別需要說明的。
2. 避免使用 new 和 delete
在需要使用 new 創建對象的場合,使用 unique_ptr 代替。例如:
#include <iostream>
#include <string>
#include <utility>
namespace c14 {
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(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<Person>("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<widget>();
...
f( *upw );
auto spw = make_shared<widget>();
...
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<mutex>{mu}; // error, not movable
auto ai = atomic<int>{0}; // error, not movable
auto a = array<int, 50>{}; // compiles, but needlessly expensive
4. 右值優化
在參數傳遞中,可以恰當地使用右值優化。
#include <string>
#include <iostream>
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 <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
6. 使用 tuple 返回多個值
這里介紹了三種方式,個人推薦第三種,更清晰一點。
#include <set>
#include <tuple>
#include <iostream>
using namespace std;
int main(int argc, char const *argv[]) {
set<string> aSet{"Hello", "World", "SB"};
string str1 = "Hello";
string str2 = "You";
string str3 = "Me";
// C98
pair<set<string>::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<string>::iterator iter;
bool success;
tie(iter, success) = aSet.insert(str3);
if (success == true) {
cout << "Inserted: " << *iter << endl;
}
return 0;
}