2
votes

Passer std :: vector à une fonction externe

J'ai affaire à une bibliothèque dont je ne peux pas modifier les types de données. J'ai besoin d'appeler une fonction qui prend un tableau bool . Le code avec lequel je travaille utilise std::vector pour stocker les booléens. J'ai beaucoup lu sur std::vector et les problèmes associés sur SO, mais je n'ai pas trouvé la solution la plus élégante à mon problème. C'est un code où la performance est cruciale. Comment résoudre ce problème de la manière la plus efficace? Je peux changer le type stocké dans std :: vector , mais je ne peux pas échapper à l'utilisation de std :: vector .

Dans mon propre problème, je dois appeler Fortran fonctionne, mais j'ai fait un exemple minimal en C ++ qui illustre le problème.

#include <cstdio>
#include <vector>

void print_c_bool(bool* bool_array, size_t n)
{
    for (int i=0; i<n; ++i)
        printf("%d, %d\n", i, bool_array[i]);
}

int main()
{
    // This works.
    bool bool_array[5] = {true, false, false, true, false};
    print_c_bool(bool_array, 5);

    // This is impossible, but how to solve it?
    std::vector<bool> bool_vector {true, false, false, true, false};
    // print_c_bool(bool_vector.data(), bool_vector.size());

    return 0;
}


10 commentaires

std::vector peut être utilisé pour contenir des 0 s ou 1 , bien que j'aimerais aussi voir une meilleure solution de contournement


Vous devez construire un tableau de booléens, c'est le seul moyen.


Qu'en est-il de ceci: std :: vector bool_vector {1, 0}; print_c_bool ((bool *) bool_vector.data (), bool_vector.size ()); ? Grossièrement.


@vahancho Non. Cela enfreint la règle stricte d'alias et ne fonctionnera pas lorsque sizeof (char)! = sizeof (bool) et dans d'autres cas.


J'ai peur que vous deviez accepter le fait que std :: vector :: data n'est pas un tableau de booléens, si vous en avez besoin, vous devez utiliser autre chose que std :: vector


@KamilCuk vous avez raison. sizeof (bool) peut ne pas être égal à 1. J'ai supprimé ma réponse car j'ai supposé que sizeof (bool) était toujours 1.


Si je freine une violation d'alias stricte, j'irais avec class Bool {bool v; }; avec au moins static_assert (sizeof (Bool) == sizeof (bool) && std :: is_pod == true) puis appelez reinterpret_cast (std :: vector :: data ()) . Semble aussi sale que les autres solutions de casting et semble fonctionner sur mon système.


Il est vraiment dommage que la norme ait défini std::vector ne pas être un vecteur de bool ! < code> Mais c'est un fait ... Mon avis est que vous devriez définir un conteneur personnalisé qui se comporte comme un vecteur pour les fonctions dont vous avez vraiment besoin. Même sans optimisation spéciale, cela ne pourrait pas être pire que de convertir à plusieurs reprises un ensemble de bits (ce qu'est un std :: vector ) et un tableau bool . Ce ne serait pas un vrai std :: vector , mais pourrait être utilisé (presque) comme si c'était ...


si vous avez un std :: vector pourquoi ne le passez-vous pas à la fonction d'impression? si vous avez également besoin de la fonction d'impression pour bool *, vous pouvez toujours en faire un modèle ...


@choosyg. Je ne peux pas faire cela, car je ne peux pas modifier ces fonctions, car il s'agit d'une bibliothèque externe.


3 Réponses :


5
votes

Vous savez ce que vous devez faire ... Créez un tableau temporaire de booléens et copiez les valeurs.

auto v = std::make_unique<bool>(bool_vector.size());
std::copy(bool_vector.begin(), bool_vector.end(), v.get());
print_c_bool(v.get(), bool_vector.size());

ou

auto v = new bool[bool_vector.size()];
std::copy(bool_vector.begin(), bool_vector.end(), v);
print_c_bool(v, bool_vector.size());
delete[] v;

p>


1 commentaires

Je voudrais éviter les copies, car c'est un gros tueur de performances.



2
votes

Je peux changer le type stocké dans le std :: vector , mais je ne peux pas m'échapper d'utiliser std :: vector .

Dans ce cas, vous n'avez pas de chance. std::vector ne stocke pas de tableau de booléens, ni ses data () ne renvoient un pointeur vers un tableau de booléens (comme tous les autres std :: vectors faire pour leur type de données respectif).

Vous pouvez jouer quelques tours et utiliser std::vector ou similaire, même si la taille correspond, un uint8_t * n'est pas un booléen * ! Étant donné que la fonction ne peut pas changer, vous ne pouvez éviter de violer l'alias strict qu'en copiant les données dans un tableau booléen.

Si vous vous souciez des performances, je vous suggère de ne pas utiliser std::vector . Par exemple, avez-vous vraiment besoin d'une taille dynamique? Sinon, utilisez un std :: array .


2 commentaires

J'ai besoin d'une taille dynamique, malheureusement. Le uint8_t semble fonctionner correctement, mais est-il garanti de fonctionner partout?


@Chiel il n'est jamais garanti de fonctionner! Vous avez manqué le message "un uint8_t * n'est pas un bool * !" Passer un uint8_t * alors qu’un bool * est attendu peut sembler fonctionner, mais cela enfreint alias strict . La règle stricte d'aliasing est délicate, car quelque chose qui semble naïvement aller ne l'est pas, et une petite optimisation du compilateur peut complètement visser votre programme



0
votes

J'ai essayé de le faire fonctionner en utilisant votre exemple et la solution suivante (assez sale) a bien fonctionné:

std::vector<Bool> b_vec {true, false, false, true, false};
test_print(reinterpret_cast<bool*>(b_vec.data()), b_vec.size());

Le résultat que j'ai obtenu est:

0: vrai
1: faux
2: faux
3: vrai
4: faux

0: vrai
1: faux
2: faux
3: vrai
4: faux

Cependant, je ne sais pas s'il se comportera de la même manière sur tous les systèmes / plates-formes. Mais si vous n'avez pas l'intention de changer votre système / plate-forme et si cela fonctionne sur le vôtre, je pense que cela pourrait faire le travail même si c'est une solution assez sale.

En fait, si nous le pouvons garantir que sizeof (bool) == sizeof (uint8_t) et en supposant que true et false sont respectivement traités en mémoire comme des entiers 1 et 0 , cette solution fonctionnera, mais pas autrement.

J'espère que cela pourra vous aider.

EDIT: Remplacé uint8_t par type char .


EDIT2:

Une autre solution qui ne viole pas la règle stricte d'aliasing est celle qui Kamil Cuk a mentionné que c'était de créer un wrapper Bool et de toujours vérifier que sizeof (bool) == sizeof (Bool) .

Une implémentation possible (juste le code de base) peut être:

struct Bool
{
    bool v;

    Bool(bool bv=false) : v(bv)
    {}
};

Et alors vous pourrez écrire:

#include <iostream>
#include <vector>

void test_print(bool * arr, size_t s)
{
    for(unsigned int i = 0; i< s; ++i)
        std::cout << i << ": " << (arr[i]?"true\n":"false\n");
    std::cout << std::endl;
}

int main()
{
    bool b_arr[5] = {true, false, false, true, false};
    test_print(b_arr, 5);

    //std::vector <uint8_t> b_vec = {true, false, false, true, false}; // former proposal
    std::vector <char> b_vec = {true, false, false, true, false}; // new proposal
    test_print(reinterpret_cast<bool*>(b_vec.data()), b_vec.size());

    return 0;
}

Cela a fonctionné pour moi :)


8 commentaires

Intéressant. Ensuite, la grande question ouverte est de savoir dans quelle mesure nous pouvons garantir cela. Des idées là-dessus?


uint8_t (si défini bien sûr) aura toujours la même taille par définition. Selon moi, cette question concerne davantage la taille de bool . Mais je ne sais pas vraiment dans quels cas c'est 1 (8 bits) et dans quels cas ce n'est pas le cas (cela dépend de la plateforme je pense).


afaik bool et uint8_t ne sont pas compatibles avec alias stricte et ce qui semble fonctionner n'est pas garanti.


@ user463035818 Si nous utilisons char au lieu de uint8_t , nous ne violons plus la règle stricte d'aliasing, et la taille de char est garantie d'être 1. Le résultat est le même. Merci d'avoir remarqué mon erreur, je ne le savais pas.


afaik l'exception est pour un char * alias un pointeur de type différent. Ce n'est pour aucun autre type qui alias un char * . Et c'est ce dernier que vous utilisez dans votre code.


vous pouvez trouver les citations pertinentes du standard ici . En un mot: vous pouvez accéder à un booléen via un pointeur char mais vous ne devez pas accéder à un caractère via un bool *


Oh tu as raison, mon mauvais. @Chiel Sans aucune optimisation du compilateur activée et si la plate-forme respecte ce que j'ai spécifié dans ma réponse, cela fonctionnera bien. C'est la seule façon que je vois pour éviter la copie. J'espère que cela aide.


J'ai édité ma réponse, c'est l'idée que @Kamil Cuk a proposée (pas la mienne) et semble fonctionner correctement.