2018年10月22日 星期一

[Effective C++] Use const whenever possible

// const: It allows you to communicate to both compilers and other programmers that a value should remain invariant

// const/non-const pointer to const/non-const data
char greeting[] = "Hello";
char * nonConstPointerNonConstData = greeting;
const char * nonConstPointerConstData = greeting;
char * const ConstPointerNonConstData = greeting;
const char * const nonConstPointerConstData = greeting;

// Both valid: function signature
void f1(const int *ptr);
void f2(int const *ptr);

// STL iterators: pointer T*
#include <vector>
void DummyFunc() {
 std::vector<int> vec;
 const std::vector<int>::iterator pIter = vec.begin(); // like T * const
 *pIter = 10; // ok
 ++pIter; // error

 std::vector<int>::const_iterator pConstIter = vec.begin(); // like T * const
 *pConstIter = 10; // error
 ++pConstIter;  // ok
}

// function const: apply on return value, parameter, entire member function
class Rational {};
const Rational operator* (const Rational& lhs, const Rational& rhs);

// If no return const, clients would be able to...
void DummyFunc() {
 Rational a, b, c;
 (a*b) = c;// invoke operator= on the result of a*b
 if (a * b = c) {} // typo, but valid
}

/* const member function */
// 1. Identify which member functions may be invoked on const objects -> for program performance(pass objects by reference-to-const)
// 2. Interface of a class easiser to understand
// 3. Member functions differing only in their constness cand be overloaded
class TextBlock {
public:
 TextBlock(const std::string& str) : text(str) {}
 const char& operator[](std::size_t position) const { return text[position]; } // for const objects
 char& operator[](std::size_t position) { return text[position]; } // for non-const objects

private:
 std::string text;
};

#include <iostream>
void DummyFunc() {
 TextBlock tb("Hello");
 const TextBlock ctb("World");

 std::cout << tb[0];   // call non-const TextBlock::operator[]
 std::cout << ctb[0];   // call const TextBlock::operator[]

 tb[0] = 'h';    // ok. If non-const TextBlock::operator[] return "char" -> error
 ctb[0] = 'w';    // error: attempt to make an assignment to a const char&
}

void print(const TextBlock& ctb) { // pass objects by reference-to-const
 std::cout << ctb[0];  // call const TextBlock::operator[]
}


/* Bitwise constness(physical constness) v.s. logical constness */
// 1. Bitwise const: a const member function iff not modify any of the object's non-static data members
// 2. Easy to detect violation: just look for assignments to data members
// 3. C++'s definition of constness

// Member function that don't act very const pass the bitwise test:
class CTextBlock {
public:
 CTextBlock(char *strText) : pText(strText) {}
 char& operator[](std::size_t position) const { return pText[position]; } // inappropriate but bitwise const
private:
 char *pText;
};

void DummyFunc() {
 const CTextBlock cctb("Hello"); // const object
 char *pc = &cctb[0];  // call const operator[]

 *pc = 'h'; // oh no! cctb now "hello"
}

// Logical constness: a const member function might modify some of the bits in the object where it's invoked, but only in ways that clients cannot detect.
class CTextBlock2 {
public:
 std::size_t length() const;

private:
 char *pText;

 mutable std::size_t textLength;
 mutable bool lengthIsValid;
 // mutable is a nice solution to bitwise-constness-is-not-what-I-had-in-mind problem
};

std::size_t CTextBlock2::length() const {
 if (!lengthIsValid) {
  textLength = std::strlen(pText); // error if no mutable
  lengthIsValid = true;   // error if no mutable
 }

 return textLength;
}

// Avoiding Duplication in const and non-const member functions
class TextBlock {
public:
 const char& operator[](std::size_t position) const {
  // heavy works: bounds checking, log access data, verify data integrity

  return text[position];
 }

 char& operator[](std::size_t position) {
  // heavy works: bounds checking, log access data, verify data integrity

  return text[position];
 }
private:
 std::string text;
};

// Solution 1: move all the same parts into a separate private member function (still duplicate function calls and return statement)
// Solution 2: implement a non-const member function in terms of its const twin (the other way around has risk to change the logical state):
class NoDupTextBlock {
public:
 const char& operator[](std::size_t position) const {
  // heavy works: bounds checking, log access data, verify data integrity

  return text[position];
 }

 char& operator[](std::size_t position) {
  return const_cast<char&>(static_cast<const NoDupTextBlock&>(*this)[position]);
 }
private:
 std::string text;
};

沒有留言:

張貼留言