Disons que j'ai la structure suivante:

template <typename T>
struct Wrapper {
    virtual T* get() const = 0;
protected:
    void *c_;
};

template <typename C, typename T>
struct WrapperOf: Wrapper<T> {

    WrapperOf(C *c = 0) : c_(c) { }

    virtual T* get() const {
        C *c = static_cast<C*>(c_);
        return static_cast<T*>(c->get());
    }
};

La norme garantit-elle que la taille de tout WrapperOf sera la même? En règle générale, pourrais-je faire ce qui suit:

struct Dummy { void* get(); };
struct Real { int* get(); };

char storage[sizeof(WrapperOf<Dummy, void>)];
Wrapper<int> *wp = 
    new(storage) WrapperOf<Real, int>();

Et si je me spécialise WrapperOf, généralement:

template <>
struct WrapperOf<void, void>: Wrapper<void> {
    virtual void* get() const { return 0; }
};

Pour l'utiliser pour initialiser le stockage (pour éviter d'avoir une classe Dummy):

char storage[sizeof(WrapperOf<void, void>)];

Serait-ce toujours valable?

1
Holt 21 avril 2017 à 16:53

3 réponses

Meilleure réponse

À partir du brouillon de travail actuel N4640 (06/02/2017)

5.3.3 Sizeof [expr.sizeof]

  1. L'opérateur sizeof donne le nombre d'octets dans la représentation objet de son opérande.

  2. ... Une fois appliqué à une classe, le résultat est le nombre d'octets dans un objet de cette classe, y compris tout remplissage requis pour placer des objets de ce type dans un tableau.

Il n'y a donc aucune garantie sur quoi que ce soit, juste que cela prend autant d'octets qu'il en faut.

Même pour la plupart des types fondamentaux, la norme dit qu'elle est définie par l'implémentation

  1. ... sizeof (char), sizeof (signe signé) et sizeof (unsigned char) sont 1. Le résultat de sizeof appliqué à tout autre type fondamental (3.9.1) est défini par l'implémentation.

On peut en déduire qu'il prend N octets pour une certaine classe et voir empiriquement qu'il en est de même pour une plage de classes dérivées dans une implémentation donnée. Il n'y a tout simplement aucune garantie.

1
Olaf Dietsche 21 avril 2017 à 14:47

Les seuls types qui ont des garanties sur la taille sont les types de disposition standard. Être polymorphe (entre autres) exclut spécifiquement cela.

Cependant, vous pouvez faire ce que vous voulez (je suppose que vous voulez allouer suffisamment d'espace pour créer tout type d'ensemble de WrapperOf):

#include <memory>
#include <tuple>

struct Dummy { void* get(); };
struct Real { int* get(); };
struct UnReal { float* get(); };

template <typename T>
struct Wrapper {
    Wrapper(void* c) : c_(c) {}
    virtual T* get() const = 0;
    void* get_c() { return c_; }
    void* get_c() const { return c_; }
protected:
    void *c_;
};


template <typename C, typename T>
struct WrapperOf: Wrapper<T> {

    WrapperOf(C *c = 0) : Wrapper<T>(c) { }

    virtual T* get() const {
        C *c = static_cast<C*>(this->get_c());
        return static_cast<T*>(c->get());
    }
};

template <>
struct WrapperOf<void, void>: Wrapper<void> {
    virtual void* get() const { return 0; }
};

template<class Type, class...Rest>
struct largest_of
{
    static constexpr auto x = sizeof(Type);
    static constexpr auto y = largest_of<Rest...>::size;
    static constexpr std::size_t size = x > y ? x : y;

    static constexpr auto q = alignof(Type);
    static constexpr auto p = largest_of<Rest...>::alignment;
    static constexpr std::size_t alignment = q > p ? q : p;
};

template<class T> struct largest_of<T>
{
    static constexpr std::size_t size = sizeof(T); 
    static constexpr std::size_t alignment = alignof(T);
};

template<class...Ts> struct largest_of<std::tuple<Ts...>> {
    static constexpr std::size_t size = largest_of<Ts...>::size; 
    static constexpr std::size_t alignment =largest_of<Ts...>::alignment;
};

using candidates = std::tuple<
    WrapperOf<Real, int>,
    WrapperOf<UnReal, float>,
    WrapperOf<Dummy, void>,
    WrapperOf<void, void>
>;

using largest = largest_of<candidates>;
std::aligned_storage<largest::size, largest::alignment> storage;

int main()
{
    auto p1 = new (std::addressof(storage)) WrapperOf<Real,int>();
    p1->~WrapperOf<Real,int>();

    auto p2 = new (std::addressof(storage)) WrapperOf<UnReal, float>();
    p2->~WrapperOf<UnReal,float>();
}
1
Richard Hodges 21 avril 2017 à 14:37

Non, il n'est garanti nulle part en standard, car le standard ne donne aucune garantie sur la taille des objets, sauf dans certains cas étroits.

Il convient également de noter que Standard ne dit rien sur les implémentations de fonctions virtuelles (des choses comme vptr n'existent pas en standard).

1
SergeyA 21 avril 2017 à 14:19