Double gratuit ou de corruption après la file d'attente::push
#include <queue>
using namespace std;
class Test{
int *myArray;
public:
Test(){
myArray = new int[10];
}
~Test(){
delete[] myArray;
}
};
int main(){
queue<Test> q
Test t;
q.push(t);
}
Après je l'exécute, j'obtiens une erreur d'exécution "double gratuit ou de corruption". Si je me débarrasser de l'destructeur de contenu (le delete
), il fonctionne très bien. Quel est le problème?
- Lire ceci puis lire ceci.
Test t();
déclare une fonction, votre code ne compile pas. vous avez besoin d'afficher le code réel.- Alors que le diaporama dans le second lien a quelque chose de bon, je ne suis pas d'accord avec eux tous, en particulier l'exemple de la page 9 montrant un local immense objet retourné par la valeur. Qui évoque à la copie de la construction qui peut être coûteux, sauf si vous êtes à l'aide de C++11 sémantique de déplacement.
- Le diaporama parle des pointeurs intelligents comme
std::unique_ptr
, qui font partie du standard C++11. Je pense qu'il est sûr de supposer que c'est ce qui implique la sémantique de déplacement, à tout le moins, si ce n'est RVO. - Lire la Règle de Trois
std::unique_ptr
est nouveau en C++11, maisstd::shared_ptr
ne l'est pas. Aussi, le diaporama mentionnestd::array
, ce qui n'est pas nouveau en C++11, soit. Donc, je ne pense pas que la sémantique de déplacement peuvent être pris en charge. Même avec RVO utilisé, copie de l'assignation serait invoquée sans C++11 assignation de déplacement.- Les deux
std::shared_ptr
etstd::array
ne sont que C++11. Il y a peut être des choses similaires en boost, mais ceux qui n'en auraient passtd::
préfixe. - en fait, ils ont été introduits dans TR1, puis mises à jour en C++11 pour correspondre à stimuler la fonctionnalité.
- Juste assez. Je n'ai jamais utiliser TR1, mais je vois votre point de vue. Même alors, cependant, il serait dans l'
tr1
espace de noms, passtd
😉 - bien joué 😛
- Cette même compiler? Ne devrait pas
Test t();
êtreTest t
? - Fixe. Il compile et a le même problème maintenant.
- Malheureusement, les réponses fournies ci-dessous sont la mauvaise façon de résoudre ce problème. Vous devriez faire
myArray
unstd::vector<int>
plutôt qu'unint*
alors tous les problèmes sont résolus (joint le C++ constructeurs de déplacement sont inclus dans le std::vector).
Vous devez vous connecter pour publier un commentaire.
Parlons de la copie d'objets en C++.
Test t;
, appelle le constructeur par défaut, qui alloue un tableau d'entiers. Ce qui est bien, et votre comportement attendu.Problème arrive lorsque vous appuyez
t
dans votre file d'attente à l'aide deq.push(t)
. Si vous êtes familier avec Java, C#, ou presque tout autre langage orienté objet, vous pouvez vous attendre l'objet que vous avez créé correspondante à être ajouté à la file d'attente, mais le C++ ne fonctionne pas de cette façon.Lorsque nous prenons un coup d'oeil à
std::queue::push
méthode, nous voyons que l'élément qui est ajouté à la file d'attente est "initialisé à une copie de x". C'est en fait une marque nouvel objet qui utilise le constructeur de copie dupliquer chaque membre de votre originalTest
un objet pour faire un nouveauTest
.Votre compilateur C++ génère un constructeur de copie par défaut! C'est assez pratique, mais les causes des problèmes avec le pointeur membres. Dans votre exemple, rappelez-vous que
int *myArray
est une adresse de mémoire; lorsque la valeur demyArray
est copié à partir de l'ancien objet de la nouvelle, vous aurez maintenant deux objets pointant vers le même tableau dans la mémoire. Ce n'est pas intrinsèquement mauvais, mais le destructeur va alors essayer de supprimer le même tableau à deux reprises, d'où le "double gratuit ou de corruption" erreur d'exécution.Comment puis-je résoudre ce problème?
La première étape est de mettre en œuvre un constructeur de copie, qui peut en toute sécurité de copier les données d'un objet à un autre. Pour des raisons de simplicité, il pourrait ressembler à quelque chose comme ceci:
Maintenant, quand vous copiez des objets d'Essai, un nouveau tableau sera alloué pour le nouvel objet, et les valeurs de la matrice seront copiés.
Nous ne sommes pas complètement hors de la difficulté encore, cependant. Il y a une autre méthode que le compilateur génère pour vous qui pourrait conduire à des problèmes similaires de cession. La différence est que, avec affectation, nous avons déjà un objet existant, dont la mémoire a besoin d'être gérés de façon appropriée. Voici une base d'affectation opérateur de mise en œuvre:
L'important est ici que nous allons copier les données dans le tableau dans cet objet du tableau, en gardant chaque objet du mémoire distinct. Nous avons également une vérification pour l'auto-affectation; autrement, nous serions copie de nous-mêmes à nous-mêmes, ce qui peut générer une erreur (pas sûr de ce qu'il est censé le faire). Si nous étions suppression et d'allouer plus de mémoire, l'auto-attribution de vérifier nous empêche de le supprimer de la mémoire à partir de laquelle nous avons besoin de la copie.
Test t();
n'est pas une déclaration de variable, mais avant la déclaration d'une fonction.myArray
àstd::vector<int>
. De cette façon, vous obtenez correctement tous la plus efficace de déplacer des composants (constructeur/affectation) qui sont définis dans C++11.other
's destructeur. Si vous voulez vraiment déplacer le tableau, entre les objets, alors vous voudrez peut-être regarder dans la sémantique de déplacement: stackoverflow.com/questions/3106110/what-are-move-semanticsmemcpy
copies octets ne sont pas des entiers. Mieux utiliserstd::copy
(tout aussi efficace mais plus sûr).Le problème, c'est que votre classe contient un gérés pointeur BRUT, mais ne pas mettre en œuvre la règle de trois (cinq en C++11). Comme un résultat que vous obtenez (évidemment) un double supprimer à cause de la copie.
Si vous êtes en apprentissage, tu dois apprendre à mettre en œuvre la règle de trois (cinq). Mais ce n'est pas la bonne solution à ce problème. Vous devriez être en utilisant la norme contenant des objets plutôt que d'essayer de gérer votre propre conteneur interne. L'exact récipient dépendra de ce que vous essayez de faire, mais std::vector est une bonne (et par défaut que vous pouvez modifier par la suite si elle n'est pas opimal).
La raison pour laquelle vous devez utiliser un conteneur standard est le
separation of concerns
. Votre classe doit être concerné par les affaires de la logique ou de la gestion des ressources (pas les deux). En supposant queTest
est quelque classe que vous utilisez pour maintenir un certain état au sujet de votre programme, alors il est logique d'entreprise et il ne devrait pas être en train de faire de la gestion des ressources. Si, d'autre partTest
est censé gérer un tableau alors vous avez probablement besoin d'apprendre plus au sujet de ce qui est disponible à l'intérieur de la bibliothèque standard.Vous obtenez double gratuit ou la corruption parce que d'abord le destructeur est pour l'objet q dans ce cas, la mémoire allouée par nouveau sera gratuit.La prochaine fois, quand detructor sera appelé pour objet t à cette époque, la mémoire est déjà libre (fait pour q) et donc lorsque dans le destructeur delete[] montableau; exécutera qu'il va jeter double gratuit ou la corruption.
La raison en est que, à la fois objet de partage la même mémoire afin de définir \copie, de cession et de l'égalité de l'opérateur comme mentionné dans la réponse ci-dessus.
Vous avez besoin de définir un constructeur de copie, de cession, de l'opérateur.
Votre copie qui est poussé sur la file d'attente vers le même mémoire, l'original est. Lorsque le premier est détruite, il supprime de la mémoire. Le second détruit et tente de les supprimer de la mémoire.
Vous pouvez également essayer de vérifier null avant de le supprimer, tels que
ou vous pouvez définir toutes les opérations de suppression de l'ina manière sûre comme ceci:
et ensuite utiliser
delete
avec un pointeur null ne pas faire n'importe quoi. Également en paramètre le pointeur à null après la suppression, il est redondant dans un destructeur, car il sera invalidé lorsque la fonction se termine.De messagerie unifiée, ne pas le destructeur être l'appel de supprimer, plutôt que de le supprimer[]?
new[]
, ergo il a besoin d'undelete[]
.delete [] myArray
. Voir la nouvelle, il n'est pasnew int
maisnew int [x]
.