没有默认构造函数的对象数组初始化

Posted

技术标签:

【中文标题】没有默认构造函数的对象数组初始化【英文标题】:Object array initialization without default constructor 【发布时间】:2011-01-21 02:08:49 【问题描述】:
#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;
    

我想创建一个汽车阵列,但出现以下错误:

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

有没有办法在不公开 Car() 构造函数的情况下进行初始化?

【问题讨论】:

operator new [] 总是调用默认构造函数。但是C++11有解决方案,如图below。 【参考方案1】:

你可以像这样使用placement-new:

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;

来自更有效的 C++ 的参考 - Scott Meyers: 第 4 条 - 避免无故使用默认构造函数

【讨论】:

为什么要反序销毁? @miniBill:因为它是按正序构建的。最后构建的东西应该是第一个销毁的东西。 如何使这个解决方案异常安全?即,如果 Car 的构造函数对某些 i 抛出异常怎么办?我想必须捕获异常,以相反的顺序解构所有已经构建的 Car 并重新抛出...... 如果我最后只做delete [] ptr;会发生什么? malloc 和 operator new 返回的内存位置保证适当对齐,以便可以将其转换为任何完整对象的指针并作为对象数组访问。换句话说,这里没有对齐问题。【参考方案2】:

没有。

但是看!如果您使用std::vector&lt;Car&gt;,就像您应该使用的那样(永远不要使用new[]),那么您可以准确指定应该如何构造元素*。

*好吧。您可以指定要复制的值。


像这样:

#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
 

附加功能:

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());
 

注意printCarNumbers 确实应该设计不同,以接受两个表示范围的迭代器。

【讨论】:

这实际上可以满足我的需要,但使用矢量不是一种选择。它必须是 Car,因为我有另一种方法采用 *Car 而不是 std::vector @Dan:您可以使用&amp;mycars[0] 获取指向底层数组的指针。虽然这通常只对将其传递给不使用 std::vector 的遗留代码有用,但如果可以,您应该更新该要求。 低级 API 通常需要一个缓冲区,有时它们需要很大(例如,从图像采集卡传输数据)。有时你无法避免它,但在绝大多数情况下你是对的 +1 嘿,伙计们,我们现在有 c++11 而不是 &amp;mycars[0],让我们使用等效的 mycars.data(),它的可读性更强。 我不是专家,但是,-1 表示“从不使用 new[]”。它的存在是有原因的。【参考方案3】:

您可以创建一个指针数组。

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;

Car() 构造函数不需要公开。将静态方法添加到构建数组的类中:

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

【讨论】:

这往往比放置新选项更干净,但是想要Car * 的代码怎么样?【参考方案4】:

在 C++11 的 std::vector 中,您可以使用 emplace_back 就地实例化元素:

  std::vector<Car> mycars;

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

瞧!

从未调用过 Car() 默认构造函数。

mycars 超出范围时会自动删除。

【讨论】:

我面临与 OP 类似的问题,但我的 mycars 包含一个引用,所以我不能使用 emplace_back (因为它可能需要使用赋值运算符,它不存在,因为引用是 const)。那么,回到 OP 最初的问题,没有办法通过不使用默认构造函数来实例化数组中的每个元素吗? 为什么需要构造函数调用引用数组【参考方案5】:

不,没有。新表达式只允许默认初始化或根本不初始化。

解决方法是使用operator new[] 分配原始内存缓冲区,然后使用带有非默认构造函数的placement-new 在该缓冲区中构造对象。

【讨论】:

当然,我们会使用std::vector,它就是这样做的。但 +1 表示正确性。 如何使用 operator new[] 分配原始内存?有什么链接可以解释吗? @Dan Paradox:Chan 的回答中有一个例子。 ITYM,new-expression的数组形式只允许默认初始化,A *a = new A(args);就可以了。这个答案也可以为 C++11 更新【参考方案6】:

好问题。我有同样的问题,并在这里找到了。真正的答案是,@Dan-Paradox,没有标准的语法方式来做到这一点。因此,所有这些答案都是解决问题的多种选择。

我自己阅读了答案,并没有发现其中任何一个特别适合我的个人惯例。我可能会坚持使用默认构造函数和set 方法:

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

标准的初始化构造函数仍然存在,所以如果我不需要多个,我仍然可以正常初始化它,但如果不是,我有一个set 方法,它设置所有在构造函数中初始化的变量.因此我可以这样做:

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

这工作正常,流畅自然,不会让代码看起来混乱。


对于那些想知道如何声明需要初始化的对象数组的人来说,这就是我的答案。

特别是对您而言,您正在尝试提供一系列汽车身份,我想您希望这些身份始终是独一无二的。你可以用我上面解释的方法来做到这一点,然后在for 循环中使用i+1 作为发送到set 方法的参数 - 但从我在你的 cmets 中读到的内容来看,似乎你想要ids 更多是在内部发起的,因此默认情况下,每个 Car 都有一个唯一的 id,即使其他人使用您的类 Car

如果这是你想要的,你可以使用静态成员:

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"

通过这种方式,您完全不必担心启动身份,因为它们是内部管理的。

【讨论】:

您已经通过使用身份循环计数器的静态存储使您的类成为非线程安全的。如果它们只需要在每个集合中是唯一的,则为循环使用局部变量更有意义,以提高效率。但是,尤其是在 C++ 中,可能没有一种简洁的方式来表达这一点。不是隐藏在构造函数中的增量。【参考方案7】:

您始终可以创建一个指针数组,指向汽车对象,然后在 for 循环中根据需要创建对象并将它们的地址保存在数组中,例如:

#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);

注意新方法!!!

  printCarNumbers_new(mycars,userInput);


  return 0;
    

在新方法中你所需要改变的只是将汽车作为指针而不是静态对象来处理 在参数中和调用方法时 printNo() 例如:

void printCarNumbers_new(Car **cars, int length)

    for(int i = 0; i<length;i++)
         std::cout<<cars[i]->printNo();

在main的末尾最好删除所有动态分配的内存

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

希望我能帮上忙,干杯!

【讨论】:

【参考方案8】:

如果出于某种原因您想将构造函数设为私有,则一种解决方法是提供静态工厂方法来分配数组。

static Car*  Car::CreateCarArray(int dimensions)

但是你为什么要保持一个构造函数是公开的而另一个是私有的呢?

但无论如何,另一种方法是使用默认值声明公共构造函数

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

【讨论】:

我将 Car() 设为私有,因为我想避免使用复制构造函数 @Dan:但这不是复制构造函数。 @GMan 你是对的,但我还是想避免使用默认构造函数。所有汽车都必须有一个 ID @Dan,在这种情况下,您可以使用带有维度和初始化列表的工厂方法。工厂方法将负责为每个对象创建、排列和初始化 id。初始化函数也可以是私有的。【参考方案9】:

没有人评论过为这项任务使用分配器的可能性。

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

【讨论】:

我不知道为什么这里没有投票,这是一个简单而好的答案。【参考方案10】:

我认为没有类型安全的方法可以做你想做的事。

【讨论】:

【参考方案11】:

您可以使用就地运算符 new。这有点可怕,我建议留在工厂。

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;

并定义一个对应的destroy,以匹配this中使用的new。

【讨论】:

我不想使用reinterpret_cast。 That's why.【参考方案12】:

我的方式

Car * cars;

// else were

extern Car * cars;

void main()

    // COLORS == id
    cars = new Car[3] 
        Car(BLUE),
            Car(RED),
            Car(GREEN)
    ;

【讨论】:

适用于少数情况,但不便携。如果你有一百个元素呢?【参考方案13】:

您可以使用 optional&lt;Car&gt; 的数组,使用 C++17 中的 optional

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

【讨论】:

【参考方案14】:

首先我想澄清一下printCarNumbers 函数中的代码存在错误,您正在尝试使用std::cout 将 void 发送到标准输出,如下所示:

void printCarNumbers(Car *cars, int length)

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

既然printNo()是用来打印的,那就直接调用吧:

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

让我们回到主题,您正在尝试使用new 分配一个对象数组,如下所示:

Car *mycars = new Car[userInput];

但是使用这种语法,你实际上试图分配一个具有userInput 大小的对象数组(这就是我们想要的),但问题是它试图为每个对象调用默认构造函数,并且默认构造函数被声明为私有的,所以它找不到它,这就是你得到那个错误的原因:

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

你需要这样做:

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

如 cmets 中所述,以这种方式调用new,无需调用默认构造函数即可为您分配内存,详情请查看new operator

现在如果你想调用参数化的构造函数,你需要为每个对象分别调用它,如下所示:

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]

你现在可能会感到困惑,因为我们再次调用了new,但是这个 new 的语法并没有分配任何内存,它只是调用索引对象的构造函数。

这里是任何想要测试的人的完整功能代码:

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

我知道我来晚了,但也许有人会觉得这很有用,如果有任何错误的说法,请随时纠正我。

【讨论】:

以上是关于没有默认构造函数的对象数组初始化的主要内容,如果未能解决你的问题,请参考以下文章

java类中构造函数中:数组怎样初始化

JAVA中怎么初始化对象数组?

mfc 类对象数组

vector

对象初始化

面向对象