Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Utiliser la VTable d'un membre

6 réponses
Avatar
Vincent Lascaux
Bonjour,

J'ai récemment été confronté au design suivant :
#include <iostream>

struct IBuffer
{
virtual void foo()
{ std::cout << "IBuffer" << std::endl; }
};
struct BufferA : public IBuffer
{
void foo()
{ std::cout << "BufferA" << std::endl; }
};
struct BufferB : public IBuffer
{
void foo()
{ std::cout << "BufferB" << std::endl; }
};

template<class Buffer>
class String
{
public:
void foo() { buffer.foo(); }
String<IBuffer>* operator &()
{ return reinterpret_cast< String<IBuffer>* >(this); }

private:
Buffer buffer;
};

void bar(String<IBuffer>* s)
{
s->foo();
}

int main()
{
String<BufferA> sa;
bar(&sa);
}


On veut pouvoir caster un String<...> en String<IBuffer>. Ceci fonctionne
sur le compilateur qu'on utilise, grâce au fait que toutes les instances de
Buffers, quelque que soit leur type (IBuffer, BufferA, BufferB), ont comme
premiers 4 octets un pointeur vers un vtable de même type.

Quelle est la façon propre de faire ca en "vrai" C++ ?
Les contraintes sont :
- on ne veut pas que la classe String grossisse (même pas d'un pointeur vers
une vtable)
- on ne veut pas forcer bar à être une fonction template

--
Vincent

6 réponses

Avatar
Frédéric Mazué
Bonjour,

J'ose la question bête qu'il ne faut sans doute pas poser :-)

A quoi sert la classe String, considérant que:

void bar(IBuffer* s)
{
s->foo();
}

int main()
{
BufferB buffer;
bar(&buffer);
}

semble suffire ? Je me permets la question car si String ne doit pas
grossir, pourquoi ne pas carrément la supprimer ? :-)

Bien cordialement

Fred


"Vincent Lascaux" a écrit dans le message de news:
44a0c7f9$0$25490$
Bonjour,

J'ai récemment été confronté au design suivant :
#include <iostream>

struct IBuffer
{
virtual void foo()
{ std::cout << "IBuffer" << std::endl; }
};
struct BufferA : public IBuffer
{
void foo()
{ std::cout << "BufferA" << std::endl; }
};
struct BufferB : public IBuffer
{
void foo()
{ std::cout << "BufferB" << std::endl; }
};

template<class Buffer>
class String
{
public:
void foo() { buffer.foo(); }
String<IBuffer>* operator &()
{ return reinterpret_cast< String<IBuffer>* >(this); }

private:
Buffer buffer;
};

void bar(String<IBuffer>* s)
{
s->foo();
}

int main()
{
String<BufferA> sa;
bar(&sa);
}


On veut pouvoir caster un String<...> en String<IBuffer>. Ceci fonctionne
sur le compilateur qu'on utilise, grâce au fait que toutes les instances
de Buffers, quelque que soit leur type (IBuffer, BufferA, BufferB), ont
comme premiers 4 octets un pointeur vers un vtable de même type.

Quelle est la façon propre de faire ca en "vrai" C++ ?
Les contraintes sont :
- on ne veut pas que la classe String grossisse (même pas d'un pointeur
vers une vtable)
- on ne veut pas forcer bar à être une fonction template

--
Vincent



Avatar
Arnaud Meurgues
Vincent Lascaux wrote:

On veut pouvoir caster un String<...> en String<IBuffer>.


N'est-il pas possible de faire un operator de cast template :

template <typename Buffer>
class String {
//...
public:
template <typename AnyBuffer>
String<AnyBuffer> operator() {
return String<AnyBuffer>(*this);
}
template <typename AnyBuffer>
String(String<AnyBuffer>& const string) : buffer(string.buffer) {}
//...
};

Quelle est la façon propre de faire ca en "vrai" C++ ?


Un constructeur template pour String prenant n'importe quel autre String
et initialisant son buffer à partir du buffer de l'autre, ça ne
conviendrait pas ?
Et si le buffer n'est pas « compatible », on a une erreur à la compilation.

--
Arnaud

Avatar
Arnaud Meurgues
Arnaud Meurgues wrote:

N'est-il pas possible de faire un operator de cast template :
[...]
template <typename AnyBuffer>
String<AnyBuffer> operator() {
return String<AnyBuffer>(*this);
}


Oups. Ça, c'était une première idée que j'ai oublié d'enlever du
message. Le constructeur devrait suffire.

Évidemment, si on passe les String par pointeur, ça ne fonctionne pas.
--
Arnaud

Avatar
Jean-Marc Bourguet
"Vincent Lascaux" writes:

Bonjour,

J'ai récemment été confronté au design suivant :
#include <iostream>

struct IBuffer
{
virtual void foo()
{ std::cout << "IBuffer" << std::endl; }
};
struct BufferA : public IBuffer
{
void foo()
{ std::cout << "BufferA" << std::endl; }
};
struct BufferB : public IBuffer
{
void foo()
{ std::cout << "BufferB" << std::endl; }
};

template<class Buffer>
class String
{
public:
void foo() { buffer.foo(); }
String<IBuffer>* operator &()
{ return reinterpret_cast< String<IBuffer>* >(this); }

private:
Buffer buffer;
};

void bar(String<IBuffer>* s)
{
s->foo();
}

int main()
{
String<BufferA> sa;
bar(&sa);
}


Si j'ai bien compris le probleme:

class StringRef
{
public:
StringRef(IBuffer& b) : buffer(&b) {}
void baz() { buffer->foo(); }
private:
IBuffer* buffer;
};

template <typename Buffer>
class String
{
public:
operator StringRef() { return StringRef(buffer); }
private:
Buffer buffer;
};

void bar(StringRef s)
{
s.baz();
}

int main()
{
String<BufferA> sa;
bar(sa);
}

Si tu peux aussi ajouter une classe StringPtr les operator-> qui vont
bien (String->StringPtr->StringRef*) pour faire en sorte que

sa->baz()

fonctionne.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Vincent Lascaux
J'ose la question bête qu'il ne faut sans doute pas poser :-)

A quoi sert la classe String


Mon exemple est trop simplifié. On tient à avoir une distinction entre
"chaine de caracteres" et "buffer". String n'offre en réalité pas la même
interface que Buffer, une String utilise un Buffer en interne, mais
l'utilisateur n'a pas à le savoir, et une String n'est pas un Buffer.

Vincent

Avatar
Vincent Lascaux
Évidemment, si on passe les String par pointeur, ça ne fonctionne pas.


Et copier un buffer est d'une part très long, d'autre part une mauvaise idée
parceque bar va alors travailler sur une mauvaise string

--
Vincent