Rule of Three
- Rule of three: If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them.
- Rule of five: From C++11 on, an object has 2 extra special member functions: the move constructor and move assignment. The rule of five states to implement these functions as well.
- Rule of zero: The zero part of the rule states that you are allowed to not write any of the special member functions when creating your class.
Copy-and-Swap Idiom
- Assignment, at its heart, is two steps:
- tearing down the object’s old state.
- building its new state as a copy of some other object’s state.
class dumb_array {
public:
// (default) constructor
dumb_array(std::size_t size = 0)
: mSize(size), mArray(mSize ? new int[mSize]() : nullptr) {}
// copy-constructor
dumb_array(const dumb_array& other)
: mSize(other.mSize), mArray(mSize ? new int[mSize] : nullptr), {
// note that this is non-throwing, because of the data
// types being used; more attention to detail with regards
// to exceptions must be given in a more general case, however
std::copy(other.mArray, other.mArray + mSize, mArray);
}
// destructor
~dumb_array() { delete[] mArray; }
friend void swap(dumb_array& first, dumb_array& second) {
// enable ADL (not necessary in our case, but good practice)
using std::swap;
// by swapping the members of two objects,
// the two objects are effectively swapped
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);
}
// move constructor
// initialize via default constructor, C++11 only
dumb_array(dumb_array&& other) noexcept : dumb_array() { swap(*this, other); }
dumb_array& operator=(dumb_array other) {
swap(*this, other);
return *this;
}
private:
std::size_t mSize;
int* mArray;
};
explicit
initialization list
numeric_limits
std::numeric_limits<float>::min()
returns the minimum positive normalized value. To find the value that has no values less than it, usestd::numeric_limits<float>::lowest()
.
accumulate
- The type of the initial value is very important.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<float> v{0.1f, 0.2f, 0.3f};
cout << accumulate(v.begin(), v.end(), 0) << endl; // Output: 0
cout << accumulate(v.begin(), v.end(), 0.f) << endl; // Output: 0.6
return 0;
}
Comparator
Error: “invalid comparator” when sorting using custom comparison function
std::sort
requires a strict weak ordering. For a strict weak ordering,comp(x, x)
must be false.
Thread safe
Circular Dependency
If both two classes contain the shared_ptr
of the other class, then these two classes will not be deleted eventually, causing memory leak.
Problematic Codes
#include <iostream>
#include <memory>
#include <unordered_map>
using namespace std;
class B;
class A {
public:
A() = default;
~A() { cout << "Deleted A!" << endl; }
public:
std::shared_ptr<B> b;
};
class B {
public:
B() = default;
~B() { cout << "Deleted B!" << endl; }
public:
std::unordered_map<int, std::shared_ptr<A>> id_to_a;
};
int main() {
auto pa = std::make_shared<A>();
auto pb = std::make_shared<B>();
pa->b = pb;
pb->id_to_a[0] = pa;
return 0;
}
When we run the codes, we will notice the destruction will not be called.
Improved Codes
#include <iostream>
#include <memory>
#include <unordered_map>
using namespace std;
class B;
class A {
public:
A(const int v) : val(v) {}
~A() { cout << "Deleted A!" << endl; }
public:
std::shared_ptr<B> b;
int val;
};
class B {
public:
B() = default;
~B() { cout << "Deleted B!" << endl; }
public:
std::unordered_map<int, std::weak_ptr<A>> id_to_a; // Hier we use 'weak_ptr'
};
int main() {
auto pa = std::make_shared<A>(250);
auto pb = std::make_shared<B>();
pa->b = pb;
pb->id_to_a[pa->val] = pa;
cout << pa.use_count() << endl; // 1
if (!pb->id_to_a[pa->val].expired()) {
auto pa2 = pb->id_to_a[pa->val].lock();
cout << pa2.use_count() << endl; // 2
cout << pa2->val << endl; // 250
}
return 0;
}
static_cast vs. dynamic_cast
Summary
dynamic_cast
主要在基类和子类间转换起作用.static_cast
可以将一个指向基类的基类指针转换成子类指针,但是,如果子类包含新的virtual
函数,我们调用这个函数时,程序会崩溃,否则,没关系(但是基类中的virtual
函数不会被重载).- 如果用
dynamic_cast
将一个指向基类的基类指针强行转换成子类指针,我们会得到一个空指针.
Example 1 - 指向基类的基类指针->子类指针
- 子类不包含新的virtual函数
#include <iostream>
using namespace std;
class Parent {
public:
virtual void Print() { cout << "I am a Parent!" << endl; }
};
class Child : public Parent {
public:
void Print() override { cout << "I am a Child!" << endl; }
// 这个函数是否为virtual会有不同的结果
void PrintPretty() { cout << "Child Only!" << endl; }
};
int main() {
Parent* parent = new Parent();
parent->Print();
cout << endl;
Child* child_dynamic = dynamic_cast<Child*>(parent);
if (child_dynamic == nullptr) {
cout << "parent cannot be converted to child" << endl;
} else {
child_dynamic->Print();
child_dynamic->PrintPretty();
}
cout << endl;
Child* child_static = static_cast<Child*>(parent);
child_static->Print(); // I am a Parent! 函数并不会被重载
child_static->PrintPretty();
return 0;
}
I am a Parent!
parent cannot be converted to child
I am a Parent!
Child Only!
- 子类包含新的virtual函数
#include <iostream>
using namespace std;
class Parent {
public:
virtual void Print() { cout << "I am a Parent!" << endl; }
};
class Child : public Parent {
public:
void Print() override { cout << "I am a Child!" << endl; }
// 这个函数是否为virtual不会有不同的结果
void PrintPretty() { cout << "Child Only!" << endl; }
};
int main() {
Parent* parent = new Parent();
parent->Print();
cout << endl;
Child* child_dynamic = dynamic_cast<Child*>(parent);
if (child_dynamic == nullptr) {
cout << "parent cannot be converted to child" << endl;
} else {
child_dynamic->Print();
child_dynamic->PrintPretty();
}
cout << endl;
Child* child_static = static_cast<Child*>(parent);
child_static->Print(); // I am a Parent! 函数并不会被重载
child_static->PrintPretty(); // segmentation fault
return 0;
}
I am a Parent!
parent cannot be converted to child
I am a Parent!
Segmentation fault (core dumped)
Example 2 - 指向子类的基类指针->子类指针
- 子类不包含新的virtual函数
#include <iostream>
using namespace std;
class Parent {
public:
virtual void Print() { cout << "I am a Parent!" << endl; }
};
class Child : public Parent {
public:
void Print() override { cout << "I am a Child!" << endl; }
void PrintPretty() { cout << "Child Only!" << endl; }
};
int main() {
Parent* parent = new Child();
parent->Print(); // I am a Child! 函数会被重载!
cout << endl;
Child* child_dynamic = dynamic_cast<Child*>(parent);
if (child_dynamic == nullptr) {
cout << "parent cannot be converted to child" << endl;
} else {
child_dynamic->Print();
child_dynamic->PrintPretty();
}
cout << endl;
Child* child_static = static_cast<Child*>(parent);
child_static->Print();
child_static->PrintPretty();
return 0;
}
I am a Child!
I am a Child!
Child Only!
I am a Child!
Child Only!
- 子类包含新的virtual函数
#include <iostream>
using namespace std;
class Parent {
public:
virtual void Print() { cout << "I am a Parent!" << endl; }
};
class Child : public Parent {
public:
void Print() override { cout << "I am a Child!" << endl; }
virtual void PrintPretty() { cout << "Child Only!" << endl; }
};
int main() {
Parent* parent = new Child();
parent->Print(); // I am a Child! 函数会被重载!
cout << endl;
Child* child_dynamic = dynamic_cast<Child*>(parent);
if (child_dynamic == nullptr) {
cout << "parent cannot be converted to child" << endl;
} else {
child_dynamic->Print();
child_dynamic->PrintPretty();
}
cout << endl;
Child* child_static = static_cast<Child*>(parent);
child_static->Print();
child_static->PrintPretty();
return 0;
}
I am a Child!
I am a Child!
Child Only!
I am a Child!
Child Only!
Error with multiple definitions of function
- Problematic source code
// main.cpp
#include <iostream>
#include "caller_a.h"
#include "caller_b.h"
int main()
{
caller_a a;
caller_b b;
a.call();
b.call();
}
// caller_a.h
#pragma once
class caller_a {
public:
void call();
};
// caller_a.cpp
#include "caller_a.h"
#include "common.h"
void caller_a::call() {
print("caller_a");
}
// caller_b.h
#pragma once
class caller_b {
public:
void call();
};
// caller_b.cpp
#include "caller_b.h"
#include "common.h"
void caller_b::call() {
print("caller_b");
}
// common.h
#pragma once
#include <iostream>
#include <string>
void print(const std::string& s) {
std::cout << s << " is called." << std::endl;
}
- Error Info
multiple definition of 'print()'
-
Solution 1:
static
// main.cpp
#include <iostream>
#include "caller_a.h"
#include "caller_b.h"
int main()
{
caller_a a;
caller_b b;
a.call();
b.call();
}
// caller_a.h
#pragma once
class caller_a {
public:
void call();
};
// caller_a.cpp
#include "caller_a.h"
#include "common.h"
void caller_a::call() {
print("caller_a");
}
// caller_b.h
#pragma once
class caller_b {
public:
void call();
};
// caller_b.cpp
#include "caller_b.h"
#include "common.h"
void caller_b::call() {
print("caller_b");
}
// common.h
#pragma once
#include <iostream>
#include <string>
static void print(const std::string& s) {
std::cout << s << " is called. (Solution 1)" << std::endl;
}
- Solution 2:
inline
// main.cpp
#include "caller_a.h"
#include "caller_b.h"
int main()
{
caller_a a;
caller_b b;
a.call();
b.call();
}
// caller_a.h
#pragma once
class caller_a {
public:
void call();
};
// caller_a.cpp
#include "caller_a.h"
#include "common.h"
void caller_a::call() {
print("caller_a");
}
// caller_b.h
#pragma once
class caller_b {
public:
void call();
};
// caller_b.cpp
#include "caller_b.h"
#include "common.h"
void caller_b::call() {
print("caller_b");
}
// common.h
#pragma once
#include <iostream>
#include <string>
inline void print(const std::string& s) {
std::cout << s << " is called. (Solution 2)" << std::endl;
}
- Solution 3: separate declaration and definition
// main.cpp
#include "caller_a.h"
#include "caller_b.h"
int main()
{
caller_a a;
caller_b b;
a.call();
b.call();
}
// caller_a.h
#pragma once
class caller_a {
public:
void call();
};
// caller_a.cpp
#include "caller_a.h"
#include "common.h"
void caller_a::call() {
print("caller_a");
}
// caller_b.h
#pragma once
class caller_b {
public:
void call();
};
// caller_b.cpp
#include "caller_b.h"
#include "common.h"
void caller_b::call() {
print("caller_b");
}
// common.h
#pragma once
#include <string>
class Common
{
public:
void print(const std::string& s);
};
// common.cpp
#include "common.h"
#include <iostream>
void Common::print(const std::string& s) {
std::cout << s << " is called. (Solution 3)" << std::endl;
}
- Solution 4:
namespace
// main.cpp
#include "caller_a.h"
#include "caller_b.h"
int main()
{
caller_a a;
caller_b b;
a.call();
b.call();
}
// caller_a.h
#pragma once
class caller_a {
public:
void call();
};
// caller_a.cpp
#include "caller_a.h"
#include "common.h"
void caller_a::call() {
print("caller_a");
}
// caller_b.h
#pragma once
class caller_b {
public:
void call();
};
// caller_b.cpp
#include "caller_b.h"
#include "common.h"
void caller_b::call() {
print("caller_b");
}
// common.h
#pragma once
#include <iostream>
#include <string>
namespace {
void print(const std::string& s) {
std::cout << s << " is called. (Solution 4)" << std::endl;
}
}
- Solution 5: multiple classes in one file
// main.cpp
#include "caller.h"
int main()
{
caller_a a;
caller_b b;
a.call();
b.call();
}
// caller.h
#pragma once
class caller_a {
public:
void call();
};
class caller_b {
public:
void call();
};
// caller.cpp
#include "caller.h"
#include "common.h"
void caller_a::call() {
print("caller_a");
}
void caller_b::call() {
print("caller_b");
}
// common.h
#pragma once
#include <iostream>
#include <string>
void print(const std::string& s) {
std::cout << s << " is called. (Solution 5)" << std::endl;
}
- Solution 6:
class
+static
// main.cpp
#include "caller_a.h"
#include "caller_b.h"
int main()
{
caller_a a;
caller_b b;
a.call();
b.call();
}
// caller_a.h
#pragma once
class caller_a {
public:
void call();
};
// caller_a.cpp
#include "caller_a.h"
#include "common.h"
void caller_a::call() {
Common::print("caller_a");
}
// caller_b.h
#pragma once
class caller_b {
public:
void call();
};
// caller_b.cpp
#include "caller_b.h"
#include "common.h"
void caller_b::call() {
Common::print("caller_b");
}
// common.h
#pragma once
#include <iostream>
#include <string>
class Common
{
public:
static void print(const std::string& s) {
std::cout << s << " is called. (Solution 6)" << std::endl;
}
};
emplace_back
vs. push_back
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<vector<int>> res;
res.push_back({1, 2, 3}); // Correct
res.emplace_back({1, 2, 3}); // Wrong: Compile Error
return 0;
}
Operator Overloading
operator[]
can only have one argument, whileoperator()
can have multiple.
new
new operator
- Call
operator new
and corresponding constructors - Cannot be overloaded
operator new
- Allocate memory but does not call constructor
- Can be overloaded
#include <cstdio>
#include <cstdlib>
#include <new>
// replacement of a minimal set of functions:
void* operator new(std::size_t sz) {
std::printf("global op new called, size = %zu\n", sz);
void *ptr = std::malloc(sz);
if (ptr)
return ptr;
else
throw std::bad_alloc{};
}
void operator delete(void* ptr) noexcept
{
std::puts("global op delete called");
std::free(ptr);
}
int main() {
int* p1 = new int;
delete p1;
int* p2 = new int[10]; // guaranteed to call the replacement in C++11
delete[] p2;
}
#include <iostream>
// class-specific allocation functions
struct X {
static void* operator new(std::size_t sz)
{
std::cout << "custom new for size " << sz << '\n';
return ::operator new(sz);
}
static void* operator new[](std::size_t sz)
{
std::cout << "custom new[] for size " << sz << '\n';
return ::operator new(sz);
}
};
int main() {
X* p1 = new X;
delete p1;
X* p2 = new X[10];
delete[] p2;
}
placement new
Placement new
allows you to construct an object in memory that’s already allocated.
// pre-allocated buffer
char *buf = new char[sizeof(string)];
// placement new
string *p = new (buf) string("hi");
// ordinary heap allocation
string *q = new string("hi");
#include <stdexcept>
#include <iostream>
struct X {
X() { throw std::runtime_error(""); }
// custom placement new
static void* operator new(std::size_t sz, bool b) {
std::cout << "custom placement new called, b = " << b << '\n';
return ::operator new(sz);
}
// custom placement delete
static void operator delete(void* ptr, bool b)
{
std::cout << "custom placement delete called, b = " << b << '\n';
::operator delete(ptr);
}
};
int main() {
try {
X* p1 = new (true) X;
} catch(const std::exception&) { }
}
#include <new> // Must #include this to use "placement new"
#include "Fred.h" // Declaration of class Fred
void someCode() {
char memory[sizeof(Fred)]; // Line #1
void* place = memory; // Line #2
Fred* f = new(place) Fred(); // Line #3 (see "DANGER" below)
// The pointers f and place will be equal
// ...
f->~Fred(); // Explicitly call the destructor for the placed object
}