TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.37K subscribers
Post #1013 4.29K
​​CRTP + friend
#опытным

Допустим, вам нужно написать tcp и udp версии клиента для одной и той же задачи. Можно использовать динамический полиморфизм и переопределить все специфические методы. Но тогда у нас будет оверхед на динамическую диспетчеризацию вызова.

Зачем тратить драгоценные миллинаносекунды, если можно воспользоваться compile-time полиморфизмом, а конкретно паттерном CRTP?

Вот сильно упрощенная и обрезанная версия того, как это может выглядеть:

template <typename Derived>
class ClientBase {
protected:
int sock_ = -1;
sockaddr_in serv_addr_{};
std::string server_ip_;
uint16_t port_;

Derived& derived() { return static_cast<Derived&>(this); }

public:
void send_message(const std::string& message) {
ssize_t bytes_sent = derived().send_impl(message);
if (bytes_sent < 0) {
throw std::runtime_error("Send failed");
}
}
};

class TcpClient : public ClientBase<TcpClient> {
public:
static constexpr int PROTOCOL_TYPE = SOCK_STREAM;

ssize_t send_impl(const std::string& message) {
return send(sock_, message.c_str(), message.length(), 0);
}

};

class UdpClient : public ClientBase<UdpClient> {
public:
static constexpr int PROTOCOL_TYPE = SOCK_DGRAM;

ssize_t send_impl(const std::string& message) {
return sendto(sock_, message.c_str(), message.length(), 0,
reinterpret_cast<sockaddr>(&serv_addr_), sizeof(serv_addr_));
}
};


И меня всегда напрягало, что в любой статье вот эти *_impl методы находятся в публичном интерфейсе наследников. Зачем они там - непонятно и это по сути детали реализации. Надо бы их скрыть.

Но как? База CRTP должна иметь доступ к этим методам.

Вот тут-то мы и используем трюк. Сделаем все члены наследников приватными и дадим базе CRTP доступ к кишкам наследников через friend:

template <typename Derived>
class ClientBase {
// ...
};

class TcpClient : public ClientBase<TcpClient> {
private:
// ...
friend class ClientBase<TcpClient>;
};

class UdpClient : public ClientBase<UdpClient> {
private:
// ...
friend class ClientBase<UdpClient>;
};


И все. Теперь все члены приватные, ClientBase имеет ко всем из них доступ и публичный интерфейс задает только ClientBase, ничего лишнего нет.

Не так много народу знает об этой технике, поэтому решил о ней отдельно рассказать.

Hide your secrets. Stay cool.

#template
  • ❤ 39
  • 👍 19
  • 🔥 15
  • 😁 6
  • 🤯 1
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →