C++ 模板类的友元和折叠规则

本文详细介绍了C++模板类中如何声明友元类和友元函数,以及实现友元类BlobPtr。同时,探讨了模板类的静态成员、类型别名、通用函数对象、尾置类型推断和引用折叠规则。通过实例展示了如何在模板类中使用这些特性,如DebugDelete仿函数和my_move操作。文章还提供了相关的视频和源码链接供深入学习。

为模板类声明友元类

有时我们需要A类访问模板类B的私有成员,但是不想其他类访问,就要在模板类B里为A类声明友元。比如我们想要实现一个BlobPtr类,让BlobPtr类成为Blob类的友元,这样BlobPtr类就可以访问Blob类了。对于Blob类的声明和定义在前文已经阐述https://llfc.club/articlepage?id=28Vv7hro3VVMPDepLTlLRLqYJhJ
我们省略Blob类的详细声明,只为它添加友元类BlobPtr类,并且为他添加友元函数operator==

class Blob
{
public:
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;
    // T类型的BlobPtr是T类型的Blob的友元类
    friend class BlobPtr<T>;
    //重载==运算符
    friend bool operator==(const Blob<T> &, const Blob<T> &);
};

实现友元类BlobPtr

接下来我们实现友元类BlobPtr,先对其进行声明

template <typename T>
class BlobPtr
{
public:
    BlobPtr() : curr(0) {}
    BlobPtr(Blob<T> &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    //递增和递减
    BlobPtr &operator++(); //前置运算符
                           // BlobPtr &operator--(); //前置运算符--

    BlobPtr &operator++(int);

private:
    std::shared_ptr<std::vector<T>>
    check(std::size_t, const std::string &) const;
    std::size_t curr; //数组中的当前位置
    //保存一个weak_ptr, 表示底层vector可能被销毁
    std::weak_ptr<std::vector<T>> wptr;
};

在实现其定义,这里只举例实现一部分函数,其余的读者自己实现即可。

template <typename T>
BlobPtr<T> &BlobPtr<T>::operator++()
{
    this->curr++;
    return *this;
}

template <typename T>
BlobPtr<T> &BlobPtr<T>::operator++(int)
{
    BlobPtr &rt = *this;
    this->curr++;
    return rt;
}

对于友元函数operator == 的定义可以按照如下实现

template <typename T>
bool operator==(const Blob<T> &b1, const Blob<T> &b2)
{
    if (b1.size() > b2.size())
    {
        return true;
    }

    if (b1.siz() < b2.size())
    {
        return false;
    }

    for (unsigned int i = 0; i < b1.size(); i++)
    {
        if (b1.data[i] == b2.data[i])
        {
            continue;
        }

        return b1.data[i] > b2.data[i];
    }

    return true;
}

模板类的友元还有一些特殊的用法,如下,读者可以自己体会

template <typename T>
class Pal
{
};

template <typename T>
class Pal2
{
};

class C
{
    // Pal<C>是C类的友元
    friend class Pal<C>;
    //所有类型的Pal2的类都是C的友元
    template <typename T>
    friend class Pal2;
};

// c2本身是一个模板类
template <typename T>
class C2
{
    //和C2同类型的Pal是C2的所有实例友元
    friend class Pal<T>;
    // Pal2的所有实例都是C2的所有实例友元
    template <typename X>
    friend class Pal2;
    // Pal3是一个普通类,他是C2的所有实例的友元
    friend class Pal3;
};

定义模板类别名

我们可以通过typedef和using等方式为一个模板类定义别名

template <typename Type>
class Bar
{
    //将访问权限授予用来实例化Bar的类型
    friend Type;
};

//定义模板类别名
typedef long long INT64;
//我们可以为实例好的模板类定义别名
typedef Bar<int> mytype;
// C11 可以为模板类定义别名
template <typename T>
using twin = pair<T, T>;
// authors 是一个pair<string, string>
twin<string> authors;
// infos 是一个pair<int, int>类型
twin<int> infos;
template <typename T>
using partNo = pair<T, unsigned>;
// books是pair<string, unsigned>类型
partNo<string> books;

类模板的静态成员

对于类模板的静态成员,其初始化要放在声明的.h文件中。

//类模板的static成员
template <typename T>
class Foo
{
public:
    static std::size_t count() { return ctr; }

private:
    static std::size_t ctr;
};

//初始化放在和声明所在的同一个.h文件中
template <typename T>
size_t Foo<T>::ctr = 0;

模板类的作用域访问

默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。
因此,如果我们希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。
我们通过使用关键字typename来实现这一点:

// 用typename 告知编译器T::value_type是一个类型
template <typename T>
typename T::value_type top(const T &c)
{
    if (!c.empty())
        return c.back();
    else
        return typename T::value_type();
}

我们定义了一个名为top的模板函数,通过T::value_type声明其返回类型,但是C++默认作用域下value_type是一个成员,
所以为了说明value_type是一个类型就需要用typename关键字做声明。

通用的函数对象

我们可以通过模板类实现通用的仿函数,也就是实现通用的函数对象,我们先实现一个DebugDelete类,用来删除各种类型的指针对象

//函数对象,给指定类型的指针执行析构
class DebugDelete
{
public:
    DebugDelete(std::ostream &s = std::cerr) : os(s) {}
    //我们定义一个仿函数,参数是T*类型
    template <typename T>
    void operator()(T *p) const
    {
        os << "deleting unique_str" << std::endl;
        delete p;
    }

private:
    std::ostream &os;
};

DebugDelete构造函数接纳一个输出流,用来在operator()调用时输出删除信息
接下来我们实现一个测试函数,用来说明DebugDelete的用法

void use_debugdel()
{
    double *p = new double;
    DebugDelete d;
    //调用DebugDelete的仿函数,delete p
    d(p);
    //析构多种类型
    int *np = new int;
    //构造DebugDelete对象后调用仿函数析构np
    DebugDelete()(np);
    //作为删除器析构智能指针
    // p 被delete时会执行DebugDelete的仿函数进行析构
    unique_ptr<int, DebugDelete> p3(new int, DebugDelete());
    // 用DebugDelete 的仿函数析构string的指针
    unique_ptr<string, DebugDelete> sp(new string, DebugDelete());
}

可以看出DebugDelete可以用来给智能指针作删除器用。

尾置类型的推断

C11新标准中提出了尾置类型推断

// func接受了一个int类型的实参,返回了一个指针,该指针指向一个含有10个整数的数组
auto func(int i) -> int (*)[10];

利用这个特性,我们可以用在模板函数中,同样实现一个尾置类型推断函数

//推断返回类型,通过尾置返回允许我们在参数列表之后的声明返回类型
template <typename It>
auto fcnrf(It beg, It end) -> decltype(*beg)
{
    //处理序列
    //返回迭代器beg指向的元素的引用
    return *beg;
}

fcnrf的返回类型时It指向元素的解引用(*beg)类型,通过decltype类型推断给出返回的类型。
我们也可以实现一个返回值类型的函数,去掉引用类型。

// remove_reference 是一个模板
// remove_reference<decltype(*beg)>::type
// type的类型就是beg指向元素的类型
// remove_reference<int&>::type type就是int
// remove_reference<string&>::type type就是string

template <typename It>
auto fcncp(It beg, It end) -> remove_reference<decltype(*beg)>
{
    //返回迭代器beg指向元素的copy
    return *beg;
}

引用折叠规则

我们可以用模板定义一个左值引用

//接受左值引用的模板函数
template <typename T>
void f1(T &t)
{
}

当我们用模板类型定义一个右值引用时,传递给该类型的实参类型,会根据C++标准进行折叠。
我们先声明一个右值引用的模板函数

//接受右值引用的模板函数
template <typename T>
void f2(T &&t)
{
}

如果我们调用f2(42), T就被推断为int
int i = 100; f2(i) T就被推断为int& 进行参数展开参数就变为int& &&
折叠后就变为int &
所以我们可以做如下归纳:
当模板函数的实参是一个T类型的右值引用
1 传递给该参数的实参是一个右值时, T就是该右值类型
2 传递给该参数的实参是一个左值时, T就是该左值引用类型。

//折叠规则
//X&& ,X&&& 都会被折叠为X&
//X&& && 会被折叠为X&&
所以根据这个规律,我们可以实现一个类似于stl的move操作

template<typename T>
typename remove_reference<T>::type && my_move(T&& t){
    return static_cast<typename remove_reference<T>::type &&>(t);
}

如果我们在函数中作如下调用

void use_tempmove()
{
    int i = 100;
    my_move(i);
    //推断规则
    /*
    1  T被推断为int &
    2  remove_reference<int &>的type成员是int
    3  my_move 的返回类型是int&&
    4  推断t类型为int& && 通过折叠规则t为int&类型
    5  最后这个表达式变为 int && my_move(int &t)
    */

    auto rb = my_move(43);
    //推断规则
    /*
    1  T被推断为int
    2  remove_reference<int>的type成员是int
    3  my_move 的返回类型为int&&
    4  my_move 的参数t类型为int &&
    5  最后这个表达式变为 int && my_move(int && t)
    */
}

总结

这篇文章介绍了模板参数类型的折叠规则和友元类的声明和使用。
视频链接https://www.bilibili.com/video/BV1cF41177hK?vd_source=8be9e83424c2ed2c9b2a3ed1d01385e9
源码链接 https://gitee.com/secondtonone1/cpplearn

<think>好的,我现在需要给用户详细讲解C++中的模板函数和模板类。用户之前已经询问过虚函数和抽象类,现在继续深入模板相关的内容,可能是在学习C++的高级特性,准备进行泛型编程或者设计更通用的代码结构。 首先,我应该确保自己正确理解模板函数和模板类的基本概念。模板是C++中实现泛型编程的重要工具,允许编写与类型无关的代码。用户可能希望了解它们的定义方式、使用场景、特化、实例化过程,以及一些高级特性如可变参数模板等。 接下来,我需要组织内容的结构。可能分为模板函数和模板类两个大部分,每个部分下再细分定义、使用、特化、注意事项等。同时,用户之前的问题回答格式使用了清晰的标题和代码示例,我应该保持一致的风格,方便用户阅读。 对于模板函数,我需要解释如何声明和定义,如何自动推导类型参数,显式指定类型,以及函数模板的重载。可能还需要提到模板参数不仅仅可以是类型,还可以是值,比如整型常量。此外,模板函数的特化和偏特化也是重要内容,但要注意函数模板不支持偏特化,只能全特化。 然后是模板类,这部分需要说明类模板的定义方式,成员函数的实现,实例化过程,以及模板类的特化和偏特化。用户可能对如何设计一个通用的容器类(如动态数组)感兴趣,因此举一个类模板的实例会有帮助。同时,嵌套模板参数和模板类的继承也是需要注意的点。 另外,用户可能会混淆模板类和类模板的术语,需要明确类模板是用于生成类的模板,而模板类通常指的是通过类模板生成的类实例。不过在实际使用中,这两个术语有时会被混用,需要指出这一点以避免混淆。 还要考虑C++11之后的新特性,比如可变参数模板,别名模板(using),模板模板参数等。这些高级内容可以放在进阶部分,供用户进一步学习参考。同时,注意事项部分需要提醒用户模板代码通常放在头文件中,因为编译器的实例化机制,以及模板带来的代码膨胀问题。 在编写过程中,要注意代码示例的正确性和简洁性。例如,模板函数的示例可以包括一个交换函数或最大值函数,模板类则可以用一个简单的Box容器作为例子。同时,需要指出常见错误,比如链接错误,模板参数不匹配等。 最后,总结对比模板函数和模板类的区别和适用场景,帮助用户更好地理解何时使用哪种模板形式。可能还需要提到模板元编程的基本概念,作为进阶学习的引导。 现在需要检查是否有遗漏的重要点,比如模板参数的非类型参数,元模板函数,或者模板与STL的关系。STL中的vector、list等都是类模板的典型例子,可以提及以增强用户的实际理解。 确保语言口语化,但保持技术准确性。避免使用过于复杂的术语,或在必要时进行解释。例如,解释“实例化”是指编译器根据模板生成具体类型的代码的过程。 可能还需要注意模板的编译过程,模板代码需要在使用时可见,因此通常将实现放在头文件中,或者在源文件中显式实例化所需类型。这一点在注意事项中需要强调,避免用户遇到链接错误。 现在整理好各个部分的结构,确保逻辑清晰,逐步引导用户从基本概念到高级特性,最后给出使用建议和注意事项。参考之前的回答格式,使用标题、代码块、列表等方式增强可读性。</think>--- ### 一、模板函数(Function Template) #### 1. 核心概念 - **泛型编程**:编写与数据类型无关的通用代码 - **自动实例化**:编译器根据调用自动生成具体类型的函数 - **语法标识**:使用`template <typename T>`声明模板参数 #### 2. 基础用法示例 ```cpp // 定义模板函数 template <typename T> T max(T a, T b) { return (a > b) ? a : b; } // 使用示例 int main() { std::cout << max(3, 5); // 输出5(int类型) std::cout << max(2.3, 1.8); // 输出2.3(double类型) std::cout << max('a', 'z'); // 输出'z'(char类型) } ``` #### 3. 关键特性 - **类型推导**:编译器自动推断模板参数类型 - **显式指定类型**: ```cpp max<double>(2, 3.14); // 强制使用double类型 ``` - **多模板参数**: ```cpp template <typename T1, typename T2> void printPair(T1 a, T2 b) { std::cout << a << " : " << b << std::endl; } ``` --- ### 二、模板类(Class Template) #### 1. 基本定义 - **通用容器**:创建可存储任意类型数据的类 - **延迟实例化**:编译器在具体使用时生成对应类型的类 - **成员函数实现**:所有成员函数必须包含模板声明 #### 2. 典型示例(动态数组容器) ```cpp template <typename T> class Box { T* data; size_t capacity; public: explicit Box(size_t size) : capacity(size) { data = new T[capacity]; } T& operator[](size_t index) { return data[index]; } ~Box() { delete[] data; } }; // 使用示例 Box<int> intBox(10); // 存储int的容器 Box<float> floatBox(5); // 存储float的容器 ``` #### 3. 高级特性 - **嵌套模板参数**: ```cpp template <template <typename> class Container, typename T> class Wrapper { Container<T> storage; }; ``` - **继承关系**: ```cpp template <typename T> class SmartBox : public Box<T> { // 继承模板类 // 新增功能实现 }; ``` --- ### 三、模板特化机制 #### 1. 全特化(Full Specialization) ```cpp // 通用模板 template <typename T> class Printer { public: void print(T value) { std::cout << "Generic: " << value << std::endl; } }; // 对int类型的全特化 template <> class Printer<int> { public: void print(int value) { std::cout << "Integer: " << value << std::endl; } }; ``` #### 2. 偏特化(Partial Specialization) ```cpp // 通用模板 template <typename T1, typename T2> class Pair { /*...*/ }; // 指针类型的偏特化 template <typename T1, typename T2> class Pair<T1*, T2*> { // 针对指针类型的特殊实现 }; ``` --- ### 四、C++11新增特性 #### 1. 可变参数模板(Variadic Templates) ```cpp template <typename... Args> void printAll(Args... args) { (std::cout << ... << args) << std::endl; // 折叠表达式 } ``` #### 2. 别名模板(Alias Template) ```cpp template <typename T> using Vec = std::vector<T, MyAllocator<T>>; // 自定义分配器的vector Vec<int> specialVector; // 等效于std::vector<int, MyAllocator<int>> ``` --- ### 五、核心对比 | 特性 | 模板函数 | 模板类 | |--------------------|-----------------------------------|-----------------------------------| | 语法声明 | `template <typename T>` | `template <typename T>` | | 主要用途 | 创建通用算法 | 创建通用容器/数据结构 | | 实例化方式 | 自动推导或显式指定 | 必须显式指定类型参数 | | 特化支持 | 支持全特化 | 支持全特化和偏特化 | | 典型应用场景 | `std::swap`, `std::sort` | `std::vector`, `std::list` | --- ### 六、开发建议 1. **模板函数适用场景** - 需要类型无关的通用算法 - 处理多种数据类型的相同操作 - 实现STL风格的通用工具函数 2. **模板类最佳实践** - 设计容器类或通用数据结构 - 实现类型安全的包装类 - 创建策略模式中的策略类 3. **注意事项** - 模板代码必须放在头文件中(无分离式编译) - 注意代码膨胀问题(过多实例化会增加二进制体积) - 使用`typename`和`class`关键字时的等价性: ```cpp template <typename T> // 推荐使用typename template <class U> // 等效声明 ``` --- ### 七、调试技巧 1. **错误信息分析** - 模板实例化失败时编译器会输出详细类型信息 - 注意检查类型约束是否满足 2. **类型检查工具** ```cpp static_assert(std::is_arithmetic<T>::value, "Requires arithmetic type"); ``` 3. **概念约束(C++20)** ```cpp template <std::integral T> // 要求整型类型 T square(T x) { return x * x; } ``` --- 通过灵活运用模板技术,可以显著提升代码的复用性和类型安全性。STL库约70%的组件基于模板实现,这是现代C++开发的核心技能之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恋恋风辰

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值