Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 26: References

Section 26.1: Defining a reference

References behaves similarly, but not entirely like const pointers. A reference is defined by su xing an ampersand & to a type name.

int i = 10; int &refi = i;

Here, refi is a reference bound to i.

References abstracts the semantics of pointers, acting like an alias to the underlying object:

refi = 20; // i = 20;

You can also define multiple references in a single definition:

int i = 10, j = 20;

int &refi = i, &refj = j;

//Common pitfall :

//int& refi = i, k = j;

//refi will be of type int&.

//though, k will be of type int, not int&!

References must be initialized correctly at the time of definition, and cannot be modified afterwards. The following piece of codes causes a compile error:

int &i; // error: declaration of reference variable 'i' requires an initializer

You also cannot bind directly a reference to nullptr, unlike pointers:

int *const ptri = nullptr;

int &refi = nullptr; // error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'nullptr_t'

GoalKicker.com – C++ Notes for Professionals

133

Chapter 27: Value and Reference

Semantics

Section 27.1: Definitions

A type has value semantics if the object's observable state is functionally distinct from all other objects of that type. This means that if you copy an object, you have a new object, and modifications of the new object will not be in any way visible from the old object.

Most basic C++ types have value semantics:

int i = 5;

int j = i; //Copied j += 20;

std::cout << i; //Prints 5; i is unaffected by changes to j.

Most standard-library defined types have value semantics too:

std::vector<int> v1(5, 12); //array of 5 values, 12 in each. std::vector<int> v2 = v1; //Copies the vector.

v2[3] = 6; v2[4] = 9;

std::cout << v1[3] << " " << v1[4]; //Writes "12 12", since v1 is unchanged.

A type is said to have reference semantics if an instance of that type can share its observable state with another object (external to it), such that manipulating one object will cause the state to change within another object.

C++ pointers have value semantics with regard to which object they point to, but they have reference semantics with regard to the state of the object they point to:

int *pi = new int(4); int *pi2 = pi;

pi = new int(16);

assert(pi2 != pi); //Will always pass.

int *pj = pi; *pj += 5;

std::cout << *pi; //Writes 9, since `pi` and `pj` reference the same object.

C++ references have reference semantics as well.

Section 27.2: Deep copying and move support

If a type wishes to have value semantics, and it needs to store objects that are dynamically allocated, then on copy operations, the type will need to allocate new copies of those objects. It must also do this for copy assignment.

This kind of copying is called a "deep copy". It e ectively takes what would have otherwise been reference semantics and turns it into value semantics:

struct Inner {int i;};

const int NUM_INNER = 5; class Value

{

private:

Inner *array_; //Normally has reference semantics.

GoalKicker.com – C++ Notes for Professionals

134

public:

Value() : array_(new Inner[NUM_INNER]){}

~Value() {delete[] array_;}

Value(const Value &val) : array_(new Inner[NUM_INNER])

{

for(int i = 0; i < NUM_INNER; ++i) array_[i] = val.array_[i];

}

Value &operator=(const Value &val)

{

for(int i = 0; i < NUM_INNER; ++i) array_[i] = val.array_[i];

return *this;

}

};

Version ≥ C++11

Move semantics allow a type like Value to avoid truly copying its referenced data. If the user uses the value in a way

that provokes a move, the "copied" from object can be left empty of the data it referenced:

struct Inner {int i;};

constexpr auto NUM_INNER = 5; class Value

{

private:

Inner *array_; //Normally has reference semantics.

public:

Value() : array_(new Inner[NUM_INNER]){}

//OK to delete even if nullptr ~Value() {delete[] array_;}

Value(const Value &val) : array_(new Inner[NUM_INNER])

{

for(int i = 0; i < NUM_INNER; ++i) array_[i] = val.array_[i];

}

Value &operator=(const Value &val)

{

for(int i = 0; i < NUM_INNER; ++i) array_[i] = val.array_[i];

return *this;

}

//Movement means no memory allocation. //Cannot throw exceptions.

Value(Value &&val) noexcept : array_(val.array_)

{

//We've stolen the old value. val.array_ = nullptr;

}

//Cannot throw exceptions.

Value &operator=(Value &&val) noexcept

{

GoalKicker.com – C++ Notes for Professionals

135

//Clever trick. Since `val` is going to be destroyed soon anyway, //we swap his data with ours. His destructor will destroy our data. std::swap(array_, val.array_);

}

};

Indeed, we can even make such a type non-copyable, if we want to forbid deep copies while still allowing the object to be moved around.

struct Inner {int i;};

constexpr auto NUM_INNER = 5; class Value

{

private:

Inner *array_; //Normally has reference semantics.

public:

Value() : array_(new Inner[NUM_INNER]){}

//OK to delete even if nullptr ~Value() {delete[] array_;}

Value(const Value &val) = delete;

Value &operator=(const Value &val) = delete;

//Movement means no memory allocation. //Cannot throw exceptions.

Value(Value &&val) noexcept : array_(val.array_)

{

//We've stolen the old value. val.array_ = nullptr;

}

//Cannot throw exceptions.

Value &operator=(Value &&val) noexcept

{

//Clever trick. Since `val` is going to be destroyed soon anyway, //we swap his data with ours. His destructor will destroy our data. std::swap(array_, val.array_);

}

};

We can even apply the Rule of Zero, through the use of unique_ptr:

struct Inner {int i;};

constexpr auto NUM_INNER = 5; class Value

{

private:

unique_ptr<Inner []>array_; //Move-only type.

public:

Value() : array_(new Inner[NUM_INNER]){}

//No need to explicitly delete. Or even declare. ~Value() = default; {delete[] array_;}

//No need to explicitly delete. Or even declare. Value(const Value &val) = default;

GoalKicker.com – C++ Notes for Professionals

136

Value &operator=(const Value &val) = default;

//Will perform an element-wise move. Value(Value &&val) noexcept = default;

//Will perform an element-wise move.

Value &operator=(Value &&val) noexcept = default;

};

GoalKicker.com – C++ Notes for Professionals

137