Автоматическое определение посетителей из CRTP (CRTP с использованием boost foreach с вариантом boost)

Мне нужно реализовать эффективное посещение вектора объектов, реализующих тот же интерфейс. До сих пор я использовал наследование с виртуальными функциями: интерфейс определяется как абстрактный класс с чисто виртуальными функциями, а каждый класс объектов реализует виртуальные функции. Вектор объектов — это просто вектор указателей на абстрактный класс (пример динамического посещения см. В конце сообщения).

Мне нужно быстрее посетить коллекцию объектов. Поскольку я знаю во время компиляции все возможные классы объекта, я использовал boost :: variable для реализации коллекции объектов (т. Е. Вектора boost :: variable). Мне нужно дополнительное определение схемы посетителя, чтобы пройти коллекцию. Чтобы сделать явным то, что все объекты реализуют один и тот же интерфейс, я использовал CRTP для получения статического наследования: интерфейс является абстракцией CRTP, а каждый класс объекта является производным от шаблонного абстрактного класса CRTP.

Вот пример реализации CRTP. Интерфейс просто определяет две функции f() а также g(double), Есть два производных класса C1 а также C2 Реализация интерфейса (с идентичным поведением).

#include <vector>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/variant.hpp>

namespace testVariantSimple
{

// Definition of the interface (abstract class).
template< typename C >
struct CBase
{
void f() { static_cast<C&>(*this).f(); }
int g(const double & x) { return static_cast<C&>(*this).g(x); }
};

// Definition of the first implementation.
struct C1 : public CBase<C1>
{
void f();
int g(const double & x);
};
void C1::f() { return ; }
int C1::g(const double & x) { return sizeof(x); }

// Definition of the second implementation.
struct C2 : public CBase<C2>
{
void f();
int g(const double & x);
};
void C2::f() { return ; }
int C2::g(const double & x) { return sizeof(x); }

// Definition of the visitor for the first function of the interface.
class f_visitor : public boost::static_visitor<int>
{
public:
template< typename C >
int operator()(CBase<C> &c ) const { c.f(); return 0; }
};

// Definition of the visitor for the second function of the interface.
struct g_visitor : public boost::static_visitor<int>
{
const double & x;
g_visitor( const double & x ) : x(x) {}
public:
template< typename C >
int operator()(CBase<C> & c) const { return c.g(x);  }
};

// Example of use: construct a random collection and visit it.
void test(int nbSample)
{
typedef boost::variant<C1,C2> CV;
std::vector<CV> vec;

for( int i=0;i<nbSample;++i )
{
switch( std::rand() % 2 )
{
case 1:       vec.push_back( C1() ); break;
case 2:       vec.push_back( C2() ); break;
}
}

double argdouble;
BOOST_FOREACH(CV & c, vec)
{
boost::apply_visitor( f_visitor(), c );
g_visitor g(argdouble);
boost::apply_visitor( g, c );
}
}
}

Этот фрагмент кода работает и в 15 раз более эффективен, чем код, использующий динамическое наследование (см. Конец сообщения для кода, использующего динамику). Код немного сложнее для чтения тем, кто не знаком с CRTP, но не труднее поддерживать или писать. Поскольку интерфейс с CRTP явный, реализация посетителя довольно тривиальна, но многословна, болезненна для понимания и использования.

Мой вопрос прост: можно ли автоматически определить посетителя из интерфейса CRTP. Я хотел бы избежать дополнительного определения f_visitor а также g_visitorи получить более читаемый вид:

   BOOST_FOREACH( CV & c, vec )
{
c.f();
c.g(argdouble);
}

Спасибо за вашу помощь. Для заинтересованных читателей, здесь тот же код, использующий виртуальное наследование.

namespace testDynamicSimple
{
struct CBase
{
virtual void f() = 0;
virtual int g(const double & x) = 0;
};

struct C1 : public CBase
{
void f() {}
int g(const double & x) { return 1; }
};
struct C2 : public CBase
{
void f() {}
int g(const double & x) { return 2; }
};

bool test(int nbSample)
{
typedef boost::shared_ptr<CBase> CV;
std::vector<CV> vec;
for( int i=0;i<nbSample;++i )
{
switch( std::rand() % 5 )
{
case 1:       vec.push_back( CV(new C1()) ); break;
case 2:       vec.push_back( CV(new C2()) ); break;
}
}

double argdouble = 0.0;
BOOST_FOREACH( CV & c, vec)
{
c->f();
c->g(argdouble);
}
}
}

2

Решение

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Это не ответ, а эталон различных подходов, которые могут быть использованы для решения данной проблемы.

Идея состоит в том, чтобы сравнить boost::variant другие методы (виртуальные функции на необработанных указателях, виртуальные функции на общих указателях и несколько других методов). Вот эталонный код, который я использовал:

#include <vector>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/variant.hpp>
#include <memory>
#include <type_traits>

namespace novirtual {

struct C {
void f() {}
int g(const double &x) { return 1; }
};

void test(int nbSample) {

std::vector<C> vec;
vec.reserve(nbSample);
for (int i = 0; i < nbSample; ++i)
vec.emplace_back(C());

double argdouble = 0.0;
BOOST_FOREACH(C &c, vec) {
c.f();
c.g(argdouble);
}
}
}
namespace crtp {

// Definition of the interface (abstract class).
template <typename C> struct CBase {
void f() { static_cast<C &>(*this).f(); }
int g(const double &x) { return static_cast<C &>(*this).g(x); }
};

// Definition of the first implementation.
struct C1 : public CBase<C1> {
void f();
int g(const double &x);
};
void C1::f() { return; }
int C1::g(const double &x) { return sizeof(x); }

// Definition of the second implementation.
struct C2 : public CBase<C2> {
void f();
int g(const double &x);
};
void C2::f() { return; }
int C2::g(const double &x) { return sizeof(x); }

// Definition of the visitor for the first function of the interface.
class f_visitor : public boost::static_visitor<int> {
public:
template <typename C> int operator()(CBase<C> &c) const {
c.f();
return 0;
}
};

// Definition of the visitor for the second function of the interface.
struct g_visitor : public boost::static_visitor<int> {
const double &x;
g_visitor(const double &x) : x(x) {}

public:
template <typename C> int operator()(CBase<C> &c) const { return c.g(x); }
};

// Example of use: construct a random collection and visit it.
void test(int nbSample) {
typedef boost::variant<C1, C2> CV;
std::vector<CV> vec;
vec.reserve(nbSample);
for (int i = 0; i < nbSample; ++i) {
switch (std::rand() % 2) {
case 0:
vec.push_back(C1());
break;
case 1:
vec.push_back(C2());
break;
}
}

double argdouble;
BOOST_FOREACH(CV & c, vec) {
boost::apply_visitor(f_visitor(), c);
g_visitor g(argdouble);
boost::apply_visitor(g, c);
}
}
}

namespace virt_fun {
struct CBase {
virtual void f() = 0;
virtual int g(const double &x) = 0;
};

struct C1 : public CBase {
void f() {}
int g(const double &x) { return 1; }
};
struct C2 : public CBase {
void f() {}
int g(const double &x) { return 2; }
};

void test(int nbSample) {
std::vector<CBase *> vec;
vec.reserve(nbSample);
for (int i = 0; i < nbSample; ++i) {
switch (std::rand() % 2) {
case 0:
vec.push_back(new C1());
break;
case 1:
vec.push_back(new C2());
break;
}
}

double argdouble = 0.0;
BOOST_FOREACH(CBase * c, vec) {
c->f();
c->g(argdouble);
}
}
}

namespace shared_ptr {
struct CBase {
virtual void f() = 0;
virtual int g(const double &x) = 0;
};

struct C1 : public CBase {
void f() {}
int g(const double &x) { return 1; }
};
struct C2 : public CBase {
void f() {}
int g(const double &x) { return 2; }
};

void test(int nbSample) {
typedef boost::shared_ptr<CBase> CV;
std::vector<CV> vec;
vec.reserve(nbSample);
for (int i = 0; i < nbSample; ++i) {
switch (std::rand() % 2) {
case 0:
vec.push_back(CV(new C1()));
break;
case 1:
vec.push_back(CV(new C2()));
break;
}
}

double argdouble = 0.0;
BOOST_FOREACH(CV & c, vec) {
c->f();
c->g(argdouble);
}
}
}

namespace virt_cont {

struct CBase {
virtual void f() = 0;
virtual int g(const double &x) = 0;
virtual ~CBase() = default;
};

struct C1 final : public CBase {
void f() {}
int g(const double &x) { return 1; }
};
struct C2 final : public CBase {
void f() {}
int g(const double &x) { return 2; }
};

struct foo {
std::aligned_storage<sizeof(C2)>::type buf;
CBase *ptr;

foo(C1 c) { ptr = new ((void *)&buf) C1(c); }
foo(C2 c) { ptr = new ((void *)&buf) C2(c); }

foo(foo &&x) : buf(x.buf) { ptr = reinterpret_cast<CBase *>(&buf); } // UB

foo &operator=(foo &&x) {
buf = x.buf;
return *this;
} // maybe UB?

~foo() { ptr->~CBase(); }
};
void test(int nbSample) {
std::vector<foo> vec;
vec.reserve(nbSample);
for (int i = 0; i < nbSample; ++i) {
switch (std::rand() % 2) {
case 0:
vec.emplace_back(C1());
break;
case 1:
vec.emplace_back(C2());
break;
}
}

double argdouble = 0.0;
BOOST_FOREACH(foo & c, vec) {
c.ptr->f();
c.ptr->g(argdouble);
}
}
}

namespace locals {
struct CBase {
virtual void f() = 0;
virtual int g(const double &x) = 0;
virtual ~CBase() = default;
};

struct C1 final : public CBase {
void f() {}
int g(const double &x) { return 1; }
};
struct C2 final : public CBase {
void f() {}
int g(const double &x) { return 2; }
};

void test(int nbSample) {
C1 c1;
C2 c2;

std::vector<CBase *> vec;
vec.reserve(nbSample);
for (int i = 0; i < nbSample; ++i) {
switch (std::rand() % 2) {
case 0:
vec[i] = &c1;
break;
case 1:
vec[i] = &c2;
break;
}
}

double argdouble = 0.0;
BOOST_FOREACH(CBase * c, vec) {
c->f();
c->g(argdouble);
}
}
}

#include <chrono>
#include <string>

#define VER 4

int main(int argc, char *argv[]) {

int n = 100000;
for (int i = 0; i < 4; ++i) {

std::chrono::time_point<std::chrono::system_clock> start, end;
start = std::chrono::system_clock::now();

#if VER == 0
virt_fun::test(n);
#elif VER == 1
shared_ptr::test(n);
#elif VER == 2
crtp::test(n);
#elif VER == 3
virt_cont::test(n);
#elif VER == 4
locals::test(n);
#endif

end = std::chrono::system_clock::now();

std::chrono::duration<double> elapsed_seconds = end - start;
std::time_t end_time = std::chrono::system_clock::to_time_t(end);

std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";

n *= 10;
}
return 0;
}

Код был скомпилирован как clang++ -O3 main.cpp -I/Users/aaragon/Local/include, с

$ clang++ --version
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.3.0
Thread model: posix

и работать на MacBook Pro с 2,6 ГГц Intel Core i7 процессор и 16 ГБ 1600 МГц DDR3 баран.

Эти результаты учитывают исправления ошибок в исходном коде, а также дополнительный код, предоставляемый @dyp, который использует класс-обертку с std::aligned_storage,
В таблице ниже no virtual Столбец не соответствует никакому наследованию и приводится как ссылка.

  |    size   | no virtual| raw ptr   | shared_ptr |  variant  | wrapper   |  locals   |
|-----------|-----------|------------|-----------|-----------|-----------|-----------|
|    100000 | 0.000235s | 0.008309s |  0.030801s | 0.003935s | 0.004222s | 0.001925s |
|   1000000 | 0.002053s | 0.061843s |  0.288403s | 0.029874s | 0.033697s | 0.01478s  |
|  10000000 | 0.017687s | 0.627659s |  2.91868s  | 0.29699s  | 0.322109s | 0.141245s |
| 100000000 | 0.177425s | 6.2493s   | 28.9586s   | 3.00427s  | 3.21402s  | 1.40478s  |

На этом этапе что-то определенное: boost::shared_ptr очень медленно, и нет никакого явного победителя между boost::variant и обертка.

1

Другие решения


По вопросам рекламы [email protected]