开发者

Object array initialization without default constructor

开发者 https://www.devze.com 2023-02-06 02:26 出处:网络
#include <iostream> class Car { private: Car(){}; int _no; public: Car(int no) { _no=no; } void printNo() {
#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;
}    

I want to create a car array but I get the following error:

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

is there a way to make this initialization without making Ca开发者_如何学Gor() constructor public?


You can use placement-new like this:

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;
}

Reference from More Effective C++ - Scott Meyers:
Item 4 - Avoid gratuitous default constructors


Nope.

But lo! If you use std::vector<Car>, like you should be (never ever use new[]), then you can specify exactly how elements should be constructed*.

*Well sort of. You can specify the value of which to make copies of.


Like this:

#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
} 

With the additional function:

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 really should be designed differently, to accept two iterators denoting a range.


You can create an array of pointers.

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;

or

Car() constructor does not need to be public. Add a static method to your class that builds an array:

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


In C++11's std::vector you can instantiate elements in-place using emplace_back:

  std::vector<Car> mycars;

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

Voila!

Car() default constructor never invoked.

Deletion will happen automatically when mycars goes out of scope.


No, there isn't. New-expression only allows default initialization or no initialization at all.

The workaround would be to allocate raw memory buffer using operator new[] and then construct objects in that buffer using placement-new with non-default constructor.


Good question. I had the same question, and found it here. The real answer is, @Dan-Paradox, there is no standard syntactical way of doing it. So, all these answers are a variety of alternatives to get around the problem.

I read the answers myself, and didn't particularly find any of them perfect for my personal convention. The method that I'll probably stick with is using a default constructor and a set method:

class MyClass
{
  int x,y,z;
public:
  MyClass(): x(0), y(0), z(0) {}
  MyClass(int _x,int _y,int _z): x(_x), y(_y), z(_z) {} // for single declarations
  void set(int _x,int _y,int _z)
  {
    x=_x;
    y=_y;
    z=_z;
  }
};

The standard initialization constructor is still there, so I can still initialize it normally if I don't need more than one, but if otherwise, I have a set method which sets all the variables that are initialized in the constructor. Thus I could do something like this:

int len = 25;
MyClass list = new MyClass[len];
for(int i = 0; i < len; i++)
  list[i].set(1, 2, 3);

This works fine and flows naturally, without making code look confusing.


Now that's my answer for those wondering how to declare an array of objects that need to be initialized.

For you specifically, you're trying to give an array of cars identities, which I'd suppose you want to always be unique. You could do it with my method I explained above, and then in the for loop use i+1 as the argument sent to the set method - but from what I've read in your comments, it seems like you want the ids more internally initiated, so that by default each Car has a unique id, even if someone else uses your class Car.

If this is what you want, you can use a static member:

class Car
{
  static int current_id;
  int id;
public:
  Car(): id(current_id++) {}

  int getId() { return id; }
};
int Car::current_id = 1;

// ...

int cars=10;
Car* carlist = new Car[cars];

for(int i = 0; i < cars; i++)
  cout << carlist[i].getId() << " "; // prints "1 2 3 4 5 6 7 8 9 10"

In this way, you don't have to worry at all about initiating the identities since they are managed internally.


You can always create an array of pointers , pointing to car objects and then create objects, in a for loop, as you want and save their address in the array , for example :

#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 new method !!!

  printCarNumbers_new(mycars,userInput);


  return 0;
}    

All you have to change in new method is handling cars as pointers than static objects in parameter and when calling method printNo() for example :

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

at the end of main is good to delete all dynamicly allocated memory like this

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

Hope I helped, cheers!


Noboby have commented the posibility of using an allocator for this task.

#include <iostream>
#include <memory>

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++)
        (cars+i)->printNo();
}

int main()
{
  int userInput = 10;

  std::allocator<Car> carAllocator;

  // reserves space in memory for 10 car objects, but not construct them
  Car *myCars = carAllocator.allocate(10); 
  Car *myCarsBegin = myCars; // begin of array myCars

  for(int i =0; i < userInput; i++ ){
      // effectively creates the class "Car" and initializes it
      // myCars now points to the first car created
      carAllocator.construct( myCars, i );
      ++myCars;    
  }
  
  printCarNumbers(myCarsBegin,userInput);

  // destroy the objects created
  for( Car *carIterator = myCarsBegin; carIterator != myCars; ++carIterator )
      carAllocator.destroy( carIterator );

  return 0;
}


One way to solve is to give a static factory method to allocate the array if for some reason you want to give constructor private.

static Car*  Car::CreateCarArray(int dimensions)

But why are you keeping one constructor public and other private?

But anyhow one more way is to declare the public constructor with default value

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


Firstly I want to clarify that there is a bug in your code in printCarNumbers function, you are trying to send void to the standard output using std::cout as shown below :

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

since printNo() is used for printing then just call it directly:

for(int i = 0; i < length; i++)
   cars[i].printNo();

let's return to the subject, you are trying to allocate an array of objects using new like this :

Car *mycars = new Car[userInput];

but with this syntax, you actually trying to allocate an array of objects that has userInput size (well that's what we want), but the problem is that it tries to call the default constructor for each object, and the default constructor is declared as private, so it can't find it, that's why you got that error :

cartest.cpp:5: error: ‘Car::Car()’ is private

instead of that you need to do it in this way :

Car *mycars = (Car*) ::operator new (sizeof(Car));
// allocates memory by calling: operator new (sizeof(Car))
// but does not call Car's constructor

as described in the comments, calling new in this way, allocates memory for you without calling the default constructor, for more details check new operator

and now if you want to call the parametrized constructors, you need to call it for each object separately as the following :

for(int i =0; i < userInput; i++)
    new (&mycars[i]) Car(i + 1);  // does not allocate memory -- calls: operator new (sizeof(Car), &mycars[i])
                                  // but constructs an object at mycars[i]

you might be confused now, since we called new again, but this syntax for new is not allocating any memory, it's just calling the constructor of the indexed object.

and here is the full functioning code for anyone wants to test :

#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++)
        cars[i].printNo();
}



int main()
{
  int userInput = 10;

  Car *mycars = (Car*) ::operator new (sizeof(Car));

  for(int i =0;i < userInput;i++)
    new (&mycars[i]) Car(i+1);

  printCarNumbers(mycars,userInput);

  return 0;
}

I know that I am so late, but maybe someone will find this useful, If there is any wrong statement, please be free to correct me.


You can use an array of optional<Car>, using optional from C++17.

#include <optional>

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

int main() {
  int userInput = 10;
  std::optional<Car> mycars[userInput];
  for (int i = 0; i < userInput; ++i)
    mycars[i].emplace(i);
  printCarNumbers(mycars, userInput);
  return 0;
}


I don't think there's type-safe method that can do what you want.


You can use in-place operator new. This would be a bit horrible, and I'd recommend keeping in a factory.

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;
}

And define a corresponding destroy so as to match the new used in this.


My way

Car * cars;

// else were

extern Car * cars;

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

精彩评论

暂无评论...
验证码 换一张
取 消