Estou explorando o novo recurso de dedução introduzido no C++23 e tentando entender como combiná-lo efetivamente com o Curiously Recurring Template Pattern (CRTP) para implementar comportamento polimórfico sem depender de funções virtuais tradicionais.
Meu objetivo é gerenciar uma coleção de objetos derivados (por exemplo, formas, processadores, etc.) de forma apagada ou semipolimórfica e ainda obter os benefícios do polimorfismo estático e possivelmente reduzir a indireção e a alocação dinâmica.
#include <vector>
#include <iostream>
template <typename Derived>
struct Base {
void process(this Derived& self) {
self.impl(); // supposed to call derived's impl()
}
};
struct DerivedA : Base<DerivedA> {
void impl() {
std::cout << "DerivedA::impl\n";
}
};
struct DerivedB : Base<DerivedB> {
void impl() {
std::cout << "DerivedB::impl\n";
}
};
int main() {
std::vector</* ??? */> collection;
collection.push_back(DerivedA{});
collection.push_back(DerivedB{});
for (auto& obj : collection) {
obj.process(); // call the correct impl() for each type
}
}
Sei que essa std::variant
é uma maneira de lidar com isso, mas estou procurando soluções alternativas de alto desempenho. Idealmente, gostaria de evitar a sobrecarga de alocações dinâmicas e funções virtuais e usar recursos modernos do C++23, como deduzir isso com CRTP, para obter os benefícios do polimorfismo estático.
você não pode usar dedução disso ou CRTP para isso,
deducing this
apenas tornou mais fácil escrever código CRTP, não mudou o fato de que você não pode ter um vetor de tipos CRTP, e você não pode ter um vetor de tipos heterogêneos, você pode ter um vetor de variantes, ou um vetor de ponteiros para tipos heterogêneos basestd::vector<std::unique_ptr<Base>>
(Base não é modelada).suas duas opções são
std::vector<std::variant<...>>
std::tuple<<std::vector<...>, ...>
basicamente um vetor para cada tipo, quando a ordem não é importante, ou você pode armazenar a ordem externamente se for necessário,boost::base_collection
faz algo semelhante e não tem ordem.como uma otimização,
std::vector<std::variant<...>>
você pode ver Como projetar um vetor mais fino de variantes em C++ - Christopher Fretz - CppCon 2024 , seu design remove a maior parte da sobrecarga de espaço de tipos heterogêneos, mantendo-os contíguos ao armazenar o tipo e o deslocamento de cada elemento externamente. (você pode compactar ambos em 8 bytes, ou na verdade menos de 8 bytes)Observação: a sobrecarga de funções virtuais e a visita a uma variante são comparáveis, sendo
std::visit
geralmente mais lentas do que uma função virtual porquestd::visit
precisam ser manipuladasvalueless_by_exception
. O que você deve se preocupar é com localidade de cache ruim e previsão incorreta de ramificação, veja O preço oculto de desempenho das funções virtuais C++ - Ivica Bogosavljevic - CppCon 2022 , portanto, se a ordem não for importante, basta usarboost::base_collection
ou rolar uma tupla de vetores.Para completar, você pode aplicar a eliminação de tipos e a otimização de pequenos buffers como
std::function
funcionam. Existem implementadores para tipos genéricos como dyno , mas se você aplicar essa "otimização" descuidadamente, sem medir quanto benefício realmente obterá, poderá acabar não usando o SBO, e essa "otimização" deixará seu código mais lento e usará mais memória sem nenhum benefício.