2013-02-25 8 views
3

gcc 4.7.1 делает пустую оптимизацию базового класса для кортежей, что я считаю очень полезной функцией. Тем не менее, по-видимому, неожиданное ограничение на это:Общий базовый класс разрывает пустую оптимизацию базового класса для кортежей

#include <tuple> 
#include <cstdint> 
#include <type_traits> 
class A { }; 
class B : public A { std::uint32_t v_; }; 
class C : public A { }; 
static_assert(sizeof(B) == 4,    "A has 32 bits."); 
static_assert(std::is_empty<C>::value,  "B is empty."); 
static_assert(sizeof(std::tuple<B, C>) == 4, "C should be 32 bits."); 

В этом случае, последнее утверждение не удается, так как кортеж, на самом деле больше, чем 4 байта. Есть ли способ избежать этого, не нарушая иерархию классов? Или мне нужно реализовать свою собственную парную реализацию, которая оптимизирует для этого случая каким-то другим способом?

+3

Возможно, [это] (http://flamingdangerzone.com/cxx11/2012/07/06/optimal-tuple-i.html) может помочь –

+0

@ AndyProwl, интересно прочитать, но я не вижу непосредственного соединения , В нем говорится, что общие реализации кортежей используют EBCO, но не указывает на то, какой вид EBCO я вижу здесь. – MvG

+5

Эта оптимизация не может применяться в вашем случае. В вашем кортеже есть ** два ** 'A' объекта, и каждый из них должен иметь другой адрес. Вам не нужно добавлять кортежи в код, вы можете добавить четвертый тип: 'struct D: B, C {};', и вы увидите, что 'sizeof (D)> sizeof (B)'. –

ответ

6

Причина, по которой пустой объект должен занимать некоторое пространство, состоит в том, что два разных объекта должны иметь разные адреса. Исключением является то, что базовый подобъект производного типа может иметь тот же адрес, что и производный полный объект (если первый нестатический член производного типа не имеет тот же тип, что и базовый [*]. пустая оптимизация базы использует это, чтобы удалить дополнительное пространство, которое добавляется в пустую базу произвольно, чтобы гарантировать, что sizeof x!=0 для любого полного объекта.

в вашем случае, кортеж держит дваA подобъектов, один является база из C другой является базой B, но они разные и поэтому они должны иметь разные адреса. Ни один из этих двух объектов не является базовым подобъектом t он другой, поэтому они не могут иметь тот же адрес. Вам даже не нужно использовать std::tuple, чтобы увидеть этот эффект, просто создать другой тип:

struct D : B, C {}; 

Размер D будет строго больше, чем размер как B и C. Чтобы убедиться, что на самом деле есть два подобъекта A, вы можете попытаться перейти к указателю на A, и компилятор с удовольствием выспит некоторую ошибку неоднозначности в вашем направлении.

[*] стандарт явно запрещает этот случай также, по той же причине:

struct A {}; 
struct B : A { A a; }; 

Опять же, в этом случае в каждом полном объекте типа B, есть два A объектов, и они должны иметь разные адреса.

2

Эта оптимизация может быть сложной задачей, так как вы можете легко увидеть свой эксперимент. Я считаю эту оптимизацию наиболее полезной, когда tuple является членом данных другого класса (особенно, если я ожидаю, что я помещаю этот класс в контейнер). Есть ли другие данные, которые вы можете связать в этом tuple, не подвергая этому факту? Например:

class D 
{ 
    std::tuple<B, int, C, int> data_; 
public: 
    B& get_B() {return std::get<0>(data_);} 
    C& get_C() {return std::get<2>(data_);} 
    int& get_size() {return std::get<1>(data_);} 
    int& get_age() {return std::get<3>(data_);} 
}; 

Для меня, и это отнюдь не гарантировано, std::tuple<B, int, C, int> всего 12 байт, и поэтому C становится оптимизированная прочь.

+0

К сожалению, в моем случае это не вариант, но полезно знать в целом. В моем случае я отбросил наследство от 'A', так как это не было абсолютно необходимо в этом конкретном случае. В других случаях могут потребоваться другие подходы, в том числе и ваши. – MvG

+1

@MvG: Наследование - одна из самых злоупотребляемых конструкций. Если это не обязательно, просто * не используйте его. –

+0

@ DavidRodríguez-dribeas: в моем случае общий предок был простым типом. Я намеревался использовать это как средство, позволяющее темплатированным операторам применяться ко всем моим классам, но не к другим классам. Я предполагаю, что вместо этого я создам отдельный предикат. Если бы у C++ были * понятия, * я, вероятно, был бы менее искушен (ab) использовать наследование для такого рода вещей. Но я все равно запомню этот урок. – MvG