You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++ CRTP无虚函数场景下仅允许直接子类访问父类方法

问题描述

假设定义了带有print()方法的Test类型,以及继承自Test的SubTest类型。现在新增继承自SubTest的SubSubTest类型,需要实现以下访问控制规则:

  • 允许SubTest访问Test的print()方法
  • 禁止SubSubTest访问Test的print()方法

本质是实现部分方法仅对直接子类开放的访问控制,所有实现基于CRTP完成,不使用虚函数。

简化示例代码如下:

class Test {
protected:
        void print() {
                std::cout << "in Test" << std::endl;
        }
};

class SubTest: public Test {
        void print() {
                Test::print();
        }
};

class SubSubTest: public SubTest {
        void print() {
                Test::print(); // 此处需要编译报错,禁止访问
        }
};

实际业务场景示例

实现目标为使用直接调用替代回调机制,完整代码如下:

#include <iostream>

enum class Type {
        whatever,
};

template <typename T>
class SSLsocket;

struct Socket {
        void send(char *buff, int size)
        {
                std::cout << "Socket send" << std::endl;
        }
        void on_message(char *buff, int size);
        Type type; // 为简化示例设为public
private:
        template <typename T>
        void dispath_message(SSLsocket<T> *sock, char *buff, int size);

        int fd;
};

template <typename T>
class SSLsocket
{
public:
        void send(char *buff, int size)
        {
                std::cout << "SSLsocket send" << std::endl;
                socket.send(buff, size);
        }

private:
        friend Socket;
        void on_message(char *buff, int size)
        {
                std::cout << "SSLsocket on_message" << std::endl;
                reinterpret_cast<T *>(this)->on_message(buff, size);
        }

private:
        Socket socket;
};

template <typename T>
class websocket : public SSLsocket<websocket<T>>
{
public:
        void send(char *buffer, int size)
        {
                std::cout << "websocket send" << std::endl;
                SSLsocket<websocket<T>>::send(buffer, size);
        }

private:
        friend SSLsocket<websocket<T>>;;
        void on_message(char *buff, int size)
        {
                std::cout << "websocket on_message" << std::endl;
                reinterpret_cast<T *>(this)->on_message(buff, size);
        }
};

class whatever : public websocket<whatever>
{
public:
        void send(char *buffer, int size)
        {
                std::cout << "whatever send" << std::endl;
                websocket<whatever>::send(buffer, size);

                // 此处必须禁止调用
                // SSLsocket<websocket<whatever>>::send(buffer, size);
        }

private:
        friend websocket<whatever>;
        void on_message(char *buff, int size)
        {
                std::cout << "whatever on_message" << std::endl;
                send(nullptr, 0);
        }
};

void Socket::on_message(char *buff, int size)
{
        std::cout << "socket on_message" << std::endl;
        switch (type) {
        case Type::whatever: {
                auto *w = reinterpret_cast<whatever *>(this);
                dispath_message(w, buff, size);
                break;
        }
        }
}

template <typename T>
void Socket::dispath_message(SSLsocket<T> *sock, char *buff, int size)
{
        sock->on_message(buff, size);
}


int main()
{
        // 类中无额外成员,为简化示例仅构造Socket类后续做类型转换
        Socket sock;
        sock.type = Type::whatever;
        sock.on_message(nullptr, 0);
}

场景约束

  • Socket类型需要感知所有final类型(例如示例中的whatever类型)完成首次类型转换
  • 已知该写法不属于常规推荐实践,但后续新增类型数量极少,可通过独立文件+编译警告约束,保证修改经过谨慎校验
  • 当前核心问题:whatever类可以直接调用SSLsocket类的send函数,不符合设计预期

调用链路规则

  • on_message调用方向:从Socket类逐层向下传递到最终final类
  • send调用方向:从final类逐层向上传递到Socket类

实现方案

使用编译期通行证(PassKey)模式实现,零运行时开销,完全兼容CRTP、无虚函数要求。

核心原理

  • 每个CRTP基类定义一个空的通行证类型,构造函数设为私有,仅将当前CRTP的模板参数(即直接子类)声明为友元
  • 需要限制仅直接子类访问的方法,增加一个该通行证类型的形参(不需要传实参值,仅做编译期权限校验)
  • 间接子类不是通行证类型的友元,无法构造通行证实例,因此无法调用对应受限方法

简化示例改造

#include <iostream>

// CRTP基类
template <typename Derived>
class Test {
protected:
    // 通行证类型,仅直接子类Derived可构造
    class PrintAccessKey {
        friend Derived;
        PrintAccessKey() = default;
    };

    void print(PrintAccessKey) {
        std::cout << "in Test" << std::endl;
    }
};

// 直接子类
class SubTest: public Test<SubTest> {
public:
    void print() {
        // 直接子类是通行证的友元,可以合法构造实例调用方法
        Test<SubTest>::print(PrintAccessKey{});
    }
};

// 间接子类
class SubSubTest: public SubTest {
public:
    void print() {
        // 编译报错:SubSubTest不是PrintAccessKey的友元,无法构造通行证,不能调用Test::print
        // Test<SubTest>::print(Test<SubTest>::PrintAccessKey{});
    }
};

实际SSLsocket场景改造

对每一层需要限制跨层调用的类增加对应通行证,修改受限方法的签名即可:

// 改造SSLsocket类
template <typename T>
class SSLsocket
{
protected:
    class SSLSendKey {
        friend T; // 仅直接子类T可构造
        SSLSendKey() = default;
    };

    // 受限的send方法,需要持有通行证才能调用
    void send(char *buff, int size, SSLSendKey)
    {
        std::cout << "SSLsocket send" << std::endl;
        socket.send(buff, size);
    }

private:
    friend Socket;
    void on_message(char *buff, int size)
    {
        std::cout << "SSLsocket on_message" << std::endl;
        reinterpret_cast<T *>(this)->on_message(buff, size);
    }

    Socket socket;
};

// 改造websocket类
template <typename T>
class websocket : public SSLsocket<websocket<T>>
{
protected:
    class WSSendKey {
        friend T; // 仅直接子类T可构造
        WSSendKey() = default;
    };

public:
    // 受限的send方法,需要持有本层通行证才能调用
    void send(char *buffer, int size, WSSendKey)
    {
        std::cout << "websocket send" << std::endl;
        // 作为SSLsocket的直接子类,可构造SSLSendKey调用父类send
        SSLsocket<websocket<T>>::send(buffer, size, SSLSendKey{});
    }

private:
    friend SSLsocket<websocket<T>>;;
    void on_message(char *buff, int size)
    {
        std::cout << "websocket on_message" << std::endl;
        reinterpret_cast<T *>(this)->on_message(buff, size);
    }
};

// 改造最终类whatever
class whatever : public websocket<whatever>
{
public:
    void send(char *buffer, int size)
    {
        std::cout << "whatever send" << std::endl;
        // 仅能调用直接父类websocket的send,可构造WSSendKey
        websocket<whatever>::send(buffer, size, WSSendKey{});

        // 编译报错:whatever不是SSLSendKey的友元,无法构造通行证,不能跨层调用SSLsocket::send
        // SSLsocket<websocket<whatever>>::send(buffer, size, {});
    }

private:
    friend websocket<whatever>;
    void on_message(char *buff, int size)
    {
        std::cout << "whatever on_message" << std::endl;
        send(nullptr, 0);
    }
};

// Socket类其余代码无需修改

方案优势

  • 所有权限校验在编译期完成,无任何运行时开销
  • 每层访问控制独立,可灵活配置哪些方法允许跨层调用、哪些仅对直接子类开放
  • 不需要修改原有调用链路逻辑,侵入性极低
  • 完全符合无虚函数、基于CRTP实现的要求

内容的提问来源于stack exchange,提问作者antho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:36:21