Chapter 9: Using Templates in Practice_《C++ Templates》notes

Using Templates in Practice

      • Step 1: Understand Template Definitions and the Inclusion Model
        • Key Concept
        • Code Example
        • Explanation
      • Step 2: Tackle Linker Errors with Explicit Instantiation
        • Key Concept
        • Code Example
        • Test Case
      • Step 3: Decode Template Error Messages
        • Key Concept
        • Code Example
        • Explanation
      • Step 4: Master the One-Definition Rule (ODR)
        • Key Concept
        • Code Example
        • Pitfall
      • Step 5: Optimize Compilation with Precompiled Headers
        • Key Concept
        • Implementation (GCC)
      • Step 6: Use C++20 Concepts for Constraints
        • Key Concept
        • Code Example
        • Explanation
      • Step 7: Organize Code for Large Templates
        • Best Practices
        • Code Example
      • Step 8: Handle Template Performance Pitfalls
        • Key Concept
        • Code Example
      • Step 9: Test Templates Thoroughly
        • Test Case Example
        • Edge Cases
      • Step 10: Avoid Common Pitfalls
        • Example
      • 10 Hard Multiple-Choice Questions
        • Questions 1-5
        • Questions 6-10
      • Answers & Explanations
      • 5 Hard Design Questions
        • Question 1: Fix Linker Error for Template Class
        • Question 2: Use C++20 Concepts to Enforce Arithmetic Types
        • Question 3: Implement Explicit Template Instantiation
        • Question 4: Optimize Compilation with Precompiled Headers
        • Question 5: Debug Template Error with `static_assert`
      • Summary


Step 1: Understand Template Definitions and the Inclusion Model

Key Concept

Templates must be fully defined in every translation unit where they are used. This is enforced by the inclusion model (defining templates in headers).

Code Example
// vector.hpp
template<typename T>
class Vector {
    T* data;
public:
    Vector(); // Declaration
    void push_back(const T& val); // Declaration
};

// Definitions must be in the header!
template<typename T>
Vector<T>::Vector() : data(nullptr) {}

template<typename T>
void Vector<T>::push_back(const T& val) { /* ... */ }
Explanation

If the definitions are not visible when the template is instantiated (e.g., in main.cpp), the linker will fail.


Step 2: Tackle Linker Errors with Explicit Instantiation

Key Concept

Linker errors occur when the compiler cannot find the template definition. Use explicit instantiation to force instantiation in a specific file.

Code Example
// vector.cpp
#include "vector.hpp"

// Explicitly instantiate for int
template class Vector<int>;
Test Case
// main.cpp
#include "vector.hpp"

int main() {
    Vector<int> v; // Works because of explicit instantiation
    v.push_back(42);
}

Step 3: Decode Template Error Messages

Key Concept

Template errors can be verbose. Use static_assert and C++20 concepts to improve diagnostics.

Code Example
template<typename T>
void print(const T& val) {
    static_assert(
        requires { std::cout << val; }, 
        "T must support operator<<"
    );
    std::cout << val;
}

int main() {
    print(10); // OK
    // print(std::vector{}); // Fails static_assert
}
Explanation

static_assert provides a clear error message if T doesn’t support operator<<.


Step 4: Master the One-Definition Rule (ODR)

Key Concept

Templates can be instantiated multiple times across translation units, but all instantiations must be identical.

Code Example
// utils.hpp
template<typename T>
T add(T a, T b) { return a + b; } // Definition in header

// main.cpp
#include "utils.hpp"
int main() { add(1, 2); } // OK: Same as other units
Pitfall

Violating ODR by modifying template behavior in different files leads to undefined behavior.


Step 5: Optimize Compilation with Precompiled Headers

Key Concept

Precompiled headers cache frequently used headers (e.g., STL, template-heavy code) to speed up compilation.

Implementation (GCC)
# Compile precompiled header
g++ -std=c++20 -xc++-header vector.hpp -o vector.hpp.gch

# Use precompiled header
g++ -std=c++20 -include vector.hpp main.cpp

Step 6: Use C++20 Concepts for Constraints

Key Concept

Concepts replace SFINAE with readable constraints.

Code Example
template<typename T>
concept Addable = requires(T a, T b) { a + b; };

template<Addable T>
T sum(T a, T b) { return a + b; }

int main() {
    sum(3, 4); // OK
    // sum(std::vector{}, std::vector{}); // Error
}
Explanation

Concepts enforce type requirements at compile time.


Step 7: Organize Code for Large Templates

Best Practices
  1. Split declarations (.hpp) and definitions (.tpp).
    2 Include the .tpp at the end of the .hpp.
Code Example
// vector.hpp
template<typename T>
class Vector { /* ... */ };

#include "vector.tpp" // Include definitions

// vector.tpp
template<typename T>
void Vector<T>::push_back(const T& val) { /* ... */ }

Step 8: Handle Template Performance Pitfalls

Key Concept

Templates can cause code bloat (multiple instantiations). Mitigate with:

  1. extern template to suppress implicit instantiation.
  2. Common base classes.
Code Example
// Explicitly instantiate in vector.cpp
extern template class Vector<int>; // Suppress implicit instantiation

Step 9: Test Templates Thoroughly

Test Case Example
void test_vector() {
    Vector<int> v;
    v.push_back(10);
    assert(v.size() == 1);
}

int main() {
    test_vector();
}
Edge Cases
  • Empty containers.
  • Move semantics.
  • Type requirements (e.g., noexcept, constexpr).

Step 10: Avoid Common Pitfalls

  1. Non-dependent names: Use typename/template to resolve dependent names.
  2. ADL pitfalls: Qualify calls to avoid unintended overloads.
  3. Exception safety: Ensure destructors are noexcept.
Example
template<typename T>
class Wrapper {
public:
    ~Wrapper() noexcept { /* Critical for containers */ }
};

10 Hard Multiple-Choice Questions

Questions 1-5

Q1: What causes a linker error when using templates?
A) Missing template declaration
B) Missing template definition in the translation unit
C) Missing inline keyword
D) Missing typename keyword

Q2: Which keyword suppresses implicit template instantiation?
A) extern
B) export
C) static
D) template

Q3: What is the purpose of static_assert in template code?
A) To validate runtime conditions
B) To enforce compile-time constraints
C) To improve runtime performance
D) To suppress compiler warnings

Q4: Which C++20 feature replaces SFINAE for type constraints?
A) constexpr
B) noexcept
C) Concepts
D) Modules

Q5: How does the One-Definition Rule (ODR) apply to templates?
A) Templates must be defined once per program
B) Template specializations can differ across translation units
C) Template definitions must be identical in all translation units
D) Template declarations must be in headers


Questions 6-10

Q6: What happens if a template definition is split into .hpp and .cpp files?
A) Compilation error
B) Linker error
C) Runtime error
D) No error

Q7: Which code violates the ODR?

// file1.cpp  
template<typename T> void foo(T) {}  

// file2.cpp  
template<typename T> void foo(T) {}  

A) Yes
B) No

Q8: What does extern template class Vector; do?
A) Forces implicit instantiation
B) Suppresses implicit instantiation
C) Declares a template specialization
D) Defines a template

Q9: Which feature reduces redundant template compilation overhead?
A) Precompiled headers
B) constexpr
C) noexcept
D) auto

Q10: Why does std::cout << typeid(T).name() fail for template types?
A) It returns a mangled name
B) It is not constexpr
C) typeid is not supported for templates
D) It requires RTTI


Answers & Explanations

  1. B (Missing template definition in the translation unit causes linker errors).
  2. A (extern template suppresses implicit instantiation).
  3. B (static_assert enforces compile-time constraints).
  4. C (Concepts replace SFINAE for readable constraints).
  5. C (ODR requires identical template definitions).
  6. B (Linker error due to missing definitions).
  7. B (Identical definitions are allowed under ODR).
  8. B (extern template suppresses implicit instantiation).
  9. A (Precompiled headers reduce compilation time).
  10. A (typeid(T).name() returns a compiler-specific mangled name).

5 Hard Design Questions

Question 1: Fix Linker Error for Template Class

Problem:

// stack.hpp  
template<typename T>  
class Stack {  
public:  
    void push(T val); // Declaration only  
};  

// main.cpp  
#include "stack.hpp"  
int main() {  
    Stack<int> s;  
    s.push(42); // Linker error  
}  

Solution:

// stack.hpp  
template<typename T>  
class Stack {  
public:  
    void push(T val) { /* Definition */ }  
};  

Test Case:

int main() {  
    Stack<int> s;  
    s.push(5);  
    assert(s.top() == 5);  
}  

Question 2: Use C++20 Concepts to Enforce Arithmetic Types

Task: Create a function sum that only accepts arithmetic types.
Solution:

template<typename T>  
requires std::is_arithmetic_v<T>  
T sum(T a, T b) { return a + b; }  

Test Case:

int main() {  
    std::cout << sum(3, 4); // OK  
    // sum("a", "b"); // Fails constraint  
}  

Question 3: Implement Explicit Template Instantiation

Task: Explicitly instantiate Vector to avoid redundant compilation.
Solution:

// vector.hpp  
template<typename T> class Vector { /* ... */ };  

// vector.cpp  
#include "vector.hpp"  
template class Vector<int>; // Explicit instantiation  

Test Case:

// main.cpp  
#include "vector.hpp"  
int main() {  
    Vector<int> v; // Uses explicit instantiation  
}  

Question 4: Optimize Compilation with Precompiled Headers

Task: Precompile vector.hpp to speed up builds.
Implementation:

g++ -std=c++20 -xc++-header vector.hpp -o vector.hpp.gch  
g++ -std=c++20 -include vector.hpp main.cpp  

Test: Measure compile time before/after precompilation.


Question 5: Debug Template Error with static_assert

Problem:

template<typename T>  
void serialize(T val) { /* Assumes T has serialize() */ }  

Solution:

template<typename T>  
void serialize(T val) {  
    static_assert(requires { val.serialize(); }, "T must have serialize()");  
}  

Test Case:

struct Data { void serialize() {} };  
int main() {  
    serialize(Data{}); // OK  
    // serialize(10); // Fails static_assert  
}  

Summary

This guide reinforces critical template concepts:

  1. Inclusion Model: Define templates in headers.
  2. Explicit Instantiation: Use extern template to control compilation.
  3. Concepts: Replace SFINAE with readable constraints.
  4. ODR: Ensure identical template definitions.
  5. Precompiled Headers: Speed up compilation.

Test cases validate solutions and ensure code correctness.

你可能感兴趣的:(c/c++,c++,开发语言,笔记)