web-dev-qa-db-fra.com

Initialisation du tableau d'objets sans constructeur par défaut

#include <iostream>
class Car
{
private:
  Car(){};
  int _no;
public:
  Car(int no)
  {
    _no=no;
  }
  void printNo()
  {
    std::cout<<_no<<std::endl;
  }
};
void printCarNumbers(Car *cars, int length)
{
    for(int i = 0; i<length;i++)
         std::cout<<cars[i].printNo();
}

int main()
{
  int userInput = 10;
  Car *mycars = new Car[userInput];
  for(int i =0;i < userInput;i++)
         mycars[i]=new Car[i+1];
  printCarNumbers(mycars,userInput);
  return 0;
}    

Je veux créer un tableau de voiture mais j'obtiens l'erreur suivante:

cartest.cpp: In function ‘int main()’:
cartest.cpp:5: error: ‘Car::Car()’ is private
cartest.cpp:21: error: within this context

est-il possible de rendre cette initialisation sans rendre public le constructeur de Car ()?

57
Dan Paradox

Nan.

Mais voilà! Si tu utilises std::vector<Car>, comme il se doit (ne jamais utiliser new[]), vous pouvez alors spécifier exactement comment les éléments doivent être construits *.

* Bien en quelque sorte. Vous pouvez spécifier la valeur à partir de laquelle faire des copies.


Comme ça:

#include <iostream>
#include <vector>

class Car
{
private:
    Car(); // if you don't use it, you can just declare it to make it private
    int _no;
public:
    Car(int no) :
    _no(no)
    {
        // use an initialization list to initialize members,
        // not the constructor body to assign them
    }

    void printNo()
    {
        // use whitespace, itmakesthingseasiertoread
        std::cout << _no << std::endl;
    }
};

int main()
{
    int userInput = 10;

    // first method: userInput copies of Car(5)
    std::vector<Car> mycars(userInput, Car(5)); 

    // second method:
    std::vector<Car> mycars; // empty
    mycars.reserve(userInput); // optional: reserve the memory upfront

    for (int i = 0; i < userInput; ++i)
        mycars.Push_back(Car(i)); // ith element is a copy of this

    // return 0 is implicit on main's with no return statement,
    // useful for snippets and short code samples
} 

Avec la fonction supplémentaire:

void printCarNumbers(Car *cars, int length)
{
    for(int i = 0; i < length; i++) // whitespace! :)
         std::cout << cars[i].printNo();
}

int main()
{
    // ...

    printCarNumbers(&mycars[0], mycars.size());
} 

Note printCarNumbers devrait vraiment être conçu différemment, pour accepter deux itérateurs désignant une plage.

38
GManNickG

Vous pouvez utiliser placement-new comme ceci:

class Car {
    int _no;
public:
    Car( int no ) :_no( no ) {
    }
};

int main() {
    void* raw_memory = operator new[]( NUM_CARS * sizeof( Car ) );
    Car* ptr = static_cast<Car*>( raw_memory );
    for( int i = 0; i < NUM_CARS; ++i ) {
        new( &ptr[i] )Car( i );
    }
    // destruct in inverse order    
    for( int i = NUM_CARS - 1; i >= 0; --i ) {
        ptr[i].~Car();
    }
    operator delete[]( raw_memory );
    return 0;
}

Référence de C++ plus efficace - Scott Meyers:
Point 4 - Évitez les constructeurs par défaut gratuits

59
Chan

Vous pouvez créer un tableau de pointeurs.

Car** mycars = new Car*[userInput];
for (int i=0; i<userInput; i++){
    mycars[i] = new Car(...);
}

...

for (int i=0; i<userInput; i++){
    delete mycars[i];
}
delete [] mycars;

ou

Le constructeur de voiture () n'a pas besoin d'être public. Ajoutez une méthode statique à votre classe qui construit un tableau:

static Car* makeArray(int length){
    return new Car[length];
}
18
Squall

Non, il n'y en a pas. La nouvelle expression n'autorise que l'initialisation par défaut ou aucune initialisation.

La solution de contournement consisterait à allouer du tampon de mémoire brute en utilisant operator new[] puis construisez des objets dans ce tampon en utilisant placement-new avec un constructeur autre que celui par défaut.

4
AnT

Bonne question. J'avais la même question et je l'ai trouvée ici. La vraie réponse est, @ Dan-Paradox, qu’il n’existe pas de méthode syntaxique standard. Toutes ces réponses sont donc diverses alternatives pour résoudre le problème.

J'ai lu les réponses moi-même et je n'ai trouvé aucune d'entre elles parfaite pour mon congrès personnel. La méthode que je vais probablement utiliser consiste à utiliser un constructeur par défaut et une méthode set:

 class MyClass 
 {
 int x, y, z; 
 public: 
 MyClass (): x (0), y (0) , z(0) {} 
 MaClasse (int _x, int _y, int _z): x (_x), y (_y), z (_z) {}// pour les déclarations simples 
 void set (int _x, int _y, int _z) 
 {
 x = _x; 
 y = _y; 
 z = _z; 
} 
}; 

Le constructeur d'initialisation standard est toujours là, donc je peux toujours l'initialiser normalement si je n'en ai pas besoin de plusieurs, mais sinon, j'ai une méthode set qui définit toutes les variables initialisées dans le constructeur. . Ainsi, je pourrais faire quelque chose comme ça:

 int len ​​= 25; 
 liste MyClass = nouvelle MyClass [len]; 
 pour (int i = 0; i <len; i ++) 
 liste [i ] .set (1,2,3); 

Cela fonctionne bien et coule naturellement, sans que le code semble déroutant.


Voilà donc ma réponse à ceux qui se demandent comment déclarer un tableau d'objets à initialiser.

Pour vous en particulier, vous essayez de donner un tableau d'identités de voitures, ce qui, je suppose, voudrait toujours être unique. Vous pouvez le faire avec la méthode que j'ai expliquée ci-dessus, puis dans la boucle for, utilisez i+1 comme argument envoyé à la méthode set - mais d'après ce que j'ai lu dans vos commentaires, il semble que vous souhaitiez que les identifiants soient initiés de manière plus interne, de sorte que, par défaut, chaque voiture possède un identifiant unique, même si quelqu'un d'autre utilise votre classe Car.

Si c'est ce que vous voulez, vous pouvez utiliser un membre statique:

 class Car 
 {
 static int current_id; 
 int id; 
 public: 
 Car (): id (current_id ++) {} 
 
 int getId () {id retour; } 
}; 
 int Car :: current_id = 1; 
 
 ... 
 
 int cars = 10; 
 Car * carlist = voiture neuve [voitures]; 
 
 Pour (int i = 0; i <voitures; i ++) 
 Cout << carlist [i]. getId () << ""; // affiche "1 2 3 4 5 6 7 8 9 10" 

De cette façon, vous n'avez pas à vous soucier du tout d'initier les identités puisqu'elles sont gérées en interne.

4
Codesmith

Vous pouvez toujours créer un tableau de pointeurs pointant sur des objets car, puis créer des objets dans une boucle for à votre guise et enregistrer leur adresse dans le tableau, par exemple:

#include <iostream>
class Car
{
private:
  Car(){};
  int _no;
public:
  Car(int no)
  {
    _no=no;
  }
  void printNo()
  {
    std::cout<<_no<<std::endl;
  }
};
void printCarNumbers(Car *cars, int length)
{
    for(int i = 0; i<length;i++)
         std::cout<<cars[i].printNo();
}

int main()
{
  int userInput = 10;
  Car **mycars = new Car*[userInput];
  int i;
  for(i=0;i<userInput;i++)
      mycars[i] = new Car(i+1);

note nouvelle méthode !!!

  printCarNumbers_new(mycars,userInput);


  return 0;
}    

Dans la nouvelle méthode, tout ce que vous avez à changer est de gérer les voitures en tant que pointeurs en tant qu'objets statiques et lorsque vous appelez la méthode, printNo () par exemple:

void printCarNumbers_new(Car **cars, int length)
{
    for(int i = 0; i<length;i++)
         std::cout<<cars[i]->printNo();
}

à la fin de main est bon de supprimer toute la mémoire allouée dynamiquement comme ceci

for(i=0;i<userInput;i++)
  delete mycars[i];      //deleting one obgject
delete[] mycars;         //deleting array of objects

J'espère que j'ai aidé, à la vôtre!

3
kondilidisn

En C++ 11 std::vector vous pouvez instancier des éléments sur place en utilisant emplace_back :

  std::vector<Car> mycars;

  for (int i = 0; i < userInput; ++i)
  {
      mycars.emplace_back(i + 1); // pass in Car() constructor arguments
  }

Voila!

Le constructeur par défaut de Car () n'a jamais été invoqué.

La suppression se produira automatiquement lorsque mycars sortira de sa portée.

3
rustyx

Une solution consiste à attribuer une méthode de fabrique statique pour allouer le tableau si, pour une raison quelconque, vous souhaitez attribuer au constructeur une valeur privée.

static Car*  Car::CreateCarArray(int dimensions)

Mais pourquoi gardez-vous un constructeur public et un autre privé?

Mais de toute façon, un autre moyen consiste à déclarer le constructeur public avec la valeur par défaut

#define DEFAULT_CAR_INIT 0
Car::Car(int _no=DEFAULT_CAR_INIT);
2
Neera

Je ne pense pas qu'il existe une méthode de type sécurisée qui puisse faire ce que vous voulez.

1
Dagang

Ma façon

Car * cars;

// else were

extern Car * cars;

void main()
{
    // COLORS == id
    cars = new Car[3] {
        Car(BLUE),
            Car(RED),
            Car(GREEN)
    };
}
0
Mitch Pisa

Vous pouvez utiliser l'opérateur new sur place. Ce serait un peu horrible, et je recommanderais de rester dans une usine.

Car* createCars(unsigned number)
{
    if (number == 0 )
        return 0;
    Car* cars = reinterpret_cast<Car*>(new char[sizeof(Car)* number]);
    for(unsigned carId = 0;
        carId != number;
        ++carId)
    {
        new(cars+carId) Car(carId);
    }
    return cars;
}

Et définissez une destruction correspondante de manière à correspondre à la nouvelle utilisation utilisée.

0
Keith