9
votes

C ++: convertir un conteneur vers un conteneur de type différent mais compatible

Il me arrive souvent d'avoir un conteneur AA C (ou quel que soit le type de classe wrapper, même des pointeurs intelligents) pour un type t1 et souhaitez convertir tellement < Code> C dans C , où t2 est compatible avec t1 . .

C ++ ne me permet pas de convertir directement tout le conteneur et de forcer un réinterpet_cast entraînerait un comportement non défini, alors je devrais créer un nouveau C Conteneur et le repeuplez-le avec C Éléments coulés comme T2 . Cette opération pourrait être assez chère, à la fois dans le temps et dans l'espace.

De plus, pour de nombreux cas, je suis sûr que forçant un réinterpret_cast fonctionnerait bien avec le code compilé par n'importe quel compilateur jamais existé, par exemple lorsque t2 est T1 const ou lorsque t1 et t2 sont des pointeurs.

Y a-t-il un moyen propre et efficace de convertir un C dans un c ?
Par exemple, un conteneur_cast opérateur (/ fonction?) Qui crée et repeupline un C si et seulement s'il ne serait pas compatible Binaire de C < T1> ?


2 commentaires

La plupart des conteneurs standard ne prennent pas en charge les types const car ils ne sont pas assignables. Pour les types de pointeur, pourquoi ne pas utiliser le type le plus général que vous devez stocker dans le conteneur? En général, lorsque vous lancez un t1 à un T2 Le résultat est un objet différent de la conversion d'un conteneur de t1 sur un conteneur de < Code> T2 implique la copie des éléments contenus. Vous ne pouvez pas éviter cette dépense.


Quelque chose de fondamentalement défectueux dans cette conception que la nécessité de lancer des conteneurs se poserait. Les conteneurs, instanciés sur différents types ne sont jamais garantis pour être compatibles, en même temps, les types qu'ils contiennent peuvent être compatibles ou non. S'ils sont compatibles, lancez le conteneur des objets, pas le conteneur lui-même.


8 Réponses :


2
votes

La raison pour laquelle vous ne pouvez pas lancer les conteneurs n'a rien à voir avec les types eux-mêmes. Le problème est que vous essayez de lancer deux objets qui sont, en ce qui concerne le compilateur et la liante, deux classes non liées.

Lorsque vous faites C code> et et et et et C code>, par exemple, le compilateur émet un code comme celui-ci: p>

class C_int_ {
    //...
};

class C_short_ {
    //...
};


8 commentaires

Je sais que reterpret_cast est dangereux pour cette raison. Avez-vous lu le troisième et quatrième paragraphe sur la question? Quelques types de types sont compatibles binaires , je cherche un moyen de laisser le compilateur reterpret_cast dans le cas où ils sont.


Oui je l'ai fait. Mais ce que je dis, c'est que même si les octets sont identiques, ils sont pas compatibles binaires. Et, rien que vous puissiez faire ne convaincra au compilateur autrement.


Bien sûr, un C va être différent d'un C . Je ne me dérangerais même pas si mon cas était comme ça. Je pensais plus explicitement à C et c . Aucun compilateur sur entendu ne générerait de code différent (sauf si C a une spécialisation pour des pointeurs constants) ...


La chose à garder à l'esprit est que le compilateur est littéralement des classes de génération comme je l'ai fait dans la réponse. À un moment donné (il y a longtemps, lorsque le compilateur C ++ ciblé c), il l'a fait dans le préprocesseur. Donc, ce n'est pas que le compilateur joue muet, c'est qu'ils sont littéralement aussi différents que classe A {int A; }; et classe B {int A;};


Oui, je ne vois toujours pas pourquoi je ne devrais pas forcer une distribution de a dans b si je sais qu'ils (la façon dont ils sont représentés en mémoire une fois compilé ) sont identiques. Je veux dire si vos classes étaient comme celles-ci: classe A {int A [1000000]; }; et classe B {int A [1000000]; }; Vous auriez une grande vitesse pour lancer directement A dans B . Étant donné que mes objets peuvent atteindre de grosses dimensions, je voudrais écrire un code qui vérifie statiquement si les types sont compatibles, et s'ils sont, il diffuse: si (statiquement_compatible :: Valeur) {forcer_cast (A); } else {slow_copy (a); } ...


@peoro: Le problème est que rien ne vous garantit qu'ils sont vraiment compatibles binaires. Vous pouvez faire des suppositions, vous pouvez même effectuer des affirmations statiques (le type de base des deux conteneurs est de la même taille, ...), mais je ne pense pas qu'il y ait un moyen d'être sûr de 100%.


@Matteo Italia: OK, je l'ai eu. Quoi qu'il en soit, je me sens très frustré à ce sujet. Le compilateur sait si deux objets sont compatibles binaires; À partir du moment où il génère l'exécutable, je peux dire si les choses fonctionnaient sans défaut. Tout ce qui me manque, c'est une construction capable de me dire au moment de la compilation si deux objets sont compatibles binaires. J'utiliserai un conteneur_cast <> qui crée une copie du conteneur. Si cela va ralentir les choses (ce que je pense peut-être probablement, car c'est dans ma boucle principale) je vais spécialiser conteneur_cast pour forcer une mise en forme de mémoire, ajout d'unités de test et note partout sur ses risques.


@peoro, @mateo Italia: le problème est plus profond que cela. Même dans les cas où les deux pourraient être compatibles binaires, permettant à la conversion ouvre la langue à d'autres types d'incohérences.



4
votes

De plus pour de nombreux cas, je suis presque sûr que forcer une réinterpret_cast fonctionnerait bien

Je vous parie que ce n'est pas le cas. Deux conteneurs stockés différents types sont jamais garantis comme compatibles binaires, même si leurs objets contenus sont. Même s'ils se produisent être compatibles binaires sous une version spécifique d'une implémentation de compilateur, il s'agit d'un détail de mise en œuvre pouvant passer d'une version mineure à la suivante.

S'appuyant sur un tel comportement sans papiers ouvre la porte à de nombreuses nuits de débogage désasturées.

Si vous souhaitez transmettre de tels conteneurs sur une fonction, faites simplement la fonction un modèle pour que les conteneurs de type arbitraire puissent être transmis. Similaire avec des cours. C'est tout le point de modèles, après tout.


3 commentaires

Bien sûr que ce n'est pas garanti, mais dans la pratique, tout compilateur stockera un std :: Set et un std :: Set dans le même Way, je vous parie de trouver n'importe quel compilateur réel ou implémentation stl qui ne le fera pas. Je pense également que cela ne devrait pas être difficile pour un compilateur d'offrir une extension non standard (macro) qui indique si elle garantit deux types sont compatibles binaires.


@Peoro: Le problème est double: (1) Ces conteneurs peuvent stocker des informations de type d'exécution; Certes, cela est peu probable en mode de libération (puisqu'il encourt une surcharge) mais tout à fait possible lors du débogage. (2) Il peut exister des spécialisations des conteneurs, même pour des types compatibles binaires qui ont une disposition de mémoire différente.


@peoro: J'ai ajouté un Répondre qui tente d'expliquer que le problème est que, permettant à ce type de conversions briserait la langue de pire des moyens que ce qu'il peut aider. Il y a un exemple concret de pourquoi même si std :: vecteur et std :: vecteur est compatible binaire, permettant à la conversion de casser la conversion const-correction dans la langue.



1
votes

Ceci est généralement difficile. Le problème devient évident lors de la prise de spécialisation des modèles, par exemple le formidable vecteur , qui a une implémentation qui diffère d'un vecteur dans beaucoup plus que le Type d'argumentation.


0 commentaires

1
votes

Il n'est absolument pas garanti que ces conteneurs sont compatibles binaires et pouvaient être couplés avec quelque chose comme Reterpret_cast <> .

Par exemple, si le conteneur (comme std :: vecteur ) stocke les données en interne dans une matrice de style C, c contiendrait un T1 [] tableau tandis que c contiendrait un t2 [] . Si maintenant t1 et t2 avoir des tailles différentes (par exemple t2 a plus de variables de membre) la mémoire du t1 [] ne peut pas simplement être interprété comme un t2 [] car les éléments de ces tableaux seraient situés à différentes positions.

Interprète donc simplement la mémoire C sous forme de C ne fonctionnera pas et une vraie conversion est nécessaire.

(En outre, il pourrait y avoir des spécialisations de modèles pour différents types, de sorte que c peut être complètement différent de C )

Pour convertir un conteneur à un autre, voir par exemple Cette question ou de nombreux autres autres.


1 commentaires

Ok, ce que je demande, cela ne va pas être possible si t1 et t2 sont incompatibles binaires. Je pensais à des cas comme C et C , où c hos n'a aucune spécialisation à propos de son conteneur Constitution: à moins que des compilateurs vraiment étranges < Code> Reterpret_cast va fonctionner correctement.



3
votes

Pourquoi ne pas utiliser le moyen sûr xxx

puis profil. S'il s'avère être un goulot d'étranglement, vous pouvez toujours revenir sur votre algorithme sous-jacent et peut-être supprimer complètement la nécessité d'une conversion complètement.

s'appuyant sur tout comportement particulier de REINTERPRET_CAST peut ne pas causer Des problèmes maintenant, mais des mois ou des années, cela provoquera presque certainement des problèmes de débogage de quelqu'un.


0 commentaires

0
votes

Ceci est en effet difficile pour les conteneurs. La compatibilité de type n'est pas suffisante, les types doivent en réalité être identique en mémoire pour éviter la tranchée lors de l'attribution. Il pourrait être possible de mettre en œuvre un PTR_Container qui expose les pointeurs d'un type compatible. Par exemple, PTR_Containerers de Boost conserve Void * s en interne de toute façon, donc les jeter à des pointeurs compatibles devrait fonctionner.

Cela dit, c'est certainement possible avec des pointeurs intelligents. Par exemple, boost :: partage_ptr implémente static_pointer_cast et dynamic_pointer_cast . .


0 commentaires

4
votes

Outre tous les autres problèmes traitants par d'autres:

  • la conversion n'implique pas la même empreinte mémoire (pensez à des opérations de conversion ...) li>
  • Spécialisations potentielles de la classe de modèle (conteneur dans votre question, mais du point de vue du compilateur, un conteneur est juste un autre modèle), même si les types sont eux-mêmes compatibles binaires li>
  • sans lien entre les différentes instanciations du même modèle (pour le cas général) li> ul>

    Il y a un problème de base dans l'approche qui n'est pas technique du tout. À condition qu'une pomme soit un fruit, aucun conteneur de fruits n'est un conteneur de pommes (trivialement démontrée) ni un conteneur de pommes ne constitue un conteneur de fruits. Essayez d'installer une pastèque dans une boîte de pommes! P>

    Aller à des détails plus techniques et traiter spécifiquement avec l'héritage où aucune conversion n'est nécessaire, (un objet dérivé est déjà em> un objet de la classe de base), si vous étiez autorisé à lancer un conteneur du type dérivé au type de base, vous pouvez ajouter des éléments non valides au conteneur: p> xxx pré>

    le La dernière ligne est parfaitement correcte: vous pouvez ajouter un Watermelon code> à un vecteur code>. Mais l'effet net est que vous avez ajouté un watermelon code> à un vecteur code>, et ce faisant, vous avez cassé le système de type. P>

    Ce n'est pas tout ce qui semble simple dans un premier regard est en fait sain d'esprit. Ceci est similaire à la raison pour laquelle vous ne pouvez pas convertir un int ** code> vers un const int ** code> même si la première pensée est qu'elle devrait être autorisée. Le fait est que, ce qui permettrait de casser la langue (dans ce cas Const Constance): P>

    std::vector<int*> v1;
    std::vector<const int*> &v2 = v1; // should this be allowed?
    const int a = 5;
    v2.push_back( &a );  // fine, v2 is a vector of pointers to constant int
                         // rather not: it IS a vector of pointers to non-const ints!
    *v1[0] = 10;         // ouch!!! a==10
    


1 commentaires

Eh bien, merci, votre réponse est celle qui me dit le mieux pourquoi je ne devrais pas du point de vue logique, même si cela fonctionnerait dans la pratique. Nous garderons à l'esprit vos exemples, ils pourraient répondre à de nombreux doutes parfois pénétraient dans mon esprit. Mon cas est un peu différent (j'ai besoin de donner l'objet: la détruira lorsque la fonction que je lui donne aux retours - c'est probablement une telle fonction d'être mauvaise conçue, ne savez pas). Maintenant, je sais pourquoi je ne devrais jamais aller chercher une solution similaire dans d'autres cas.



3
votes

OK, laissez-moi résumer le tout.

Vos réponses (correctes!) Dites qu'en C ++ Compatibilité binaire em> est jamais forte> garantie pour différents types. Il est indéfini du comportement de prendre la valeur d'une zone de mémoire dans laquelle une variable est située et l'utiliser pour une variable d'un type différent (et cela devrait également être évité également avec des variables du même type). P>

Également dans la vie réelle, cette chose pourrait être dangereuse, même pour les objets em> simples em>, sans parler des conteneurs! P>

*: par compatibilité binaire em> i signifie que les mêmes valeurs sont stockées dans la mémoire de la même manière et que les mêmes instructions d'assemblage sont utilisées de la même manière pour le manipuler. par exemple: même si float code> et int code> sont 4 octets chacun, ils ne sont pas compatibles binaires em>. SUB> P>


Cependant, je ne suis pas satisfait de cette règle C ++ : concentrons sur un seul cas, comme sur ces deux structures: struct A {int A [1000000]; }; code> et struct b {int A [1000000]; }; code>. p>

Nous ne pouvons pas simplement utiliser l'adresse d'un objet A code> comme s'il s'agissait d'un B code> un. Et cela me frustre pour les raisons suivantes: p>

  • Le compilateur sait statilement si ces structures sont compatibles binaires em>: une fois que l'exécutable a été généré, vous pouviez le regarder et dire si elles sont telles. Juste it (le compilateur) ne nous donne pas ces informations. P> li>

  • Autant que je sache que tout compilateur C ++ existait a déjà existé des données dans un cohérent em>. Je ne peux même pas imaginer un compilateur générant des représentations différentes pour ces deux structures. Le point qui me bugs le plus est que non seulement ces simples A code> et B code> sont compatibles binaires em>, mais à propos de tout conteneur est, si vous Utilisez-le avec des types que vous pouvez vous attendre à être compatible binaire em> (j'ai rencontré des tests avec GCC 4.5 et CLANG 2.8 sur des conteneurs personnalisés et des conteneurs sur mesure et des stl / boost). P> LI>

  • Les opérateurs de casting permettent au compilateur faire ce que je cherche à faire, mais seulement avec des types de base. Si vous lancez un int code> comme const int code> (ou un int * code> et un char * code>), et ces deux Les types sont compatibles binaires em>, le compilateur peut (probablement) éviter de en faire une copie et utiliser simplement les mêmes octets bruts. P> Li> ul>


    Mon idée est alors de créer une liste personnalisée objet_static_cast code> qui vérifiera si l'objet du type utilisé et que l'objet du type à lancer est compatible binaire em>; S'ils sont simplement renvoyant la référence casted, sinon, cela construira un nouvel objet et le retournera. P>

    espoir de ne pas trop être évité pour cette réponse; Je vais supprimer si la communauté ne l'aime pas. P>

    Pour vérifier si deux types sont compatibles binaires em> introduits un nouveau trait de type: P>

    $ time ./bnicmop 
    Allocating Data
    Destroying Data
    
    real    0m0.123s
    user    0m0.087s
    sys     0m0.017s
    


0 commentaires