【问题标题】:Allocating memory without initializing it in C++分配内存而不用 C++ 初始化它
【发布时间】:2011-12-26 00:47:07
【问题描述】:

我正在熟悉 C++,但我遇到了内存管理问题。在 C 中,每当我想为任意数量的元素保留内存时,无论类型如何,我都会调用malloc(),然后手动(通过循环)初始化为我想要的任何值。使用 C++ 的new,一切都会自动初始化。

问题是,我有一个 BattlePoint 类,它有点像这样:

class BattlePoint {
public:
    BattlePoint(int x, int y) : x(x), y(y) { };
    bool operator==(const BattlePoint &right);
    virtual ~BattlePoint();

private:
    int x, y;
};

如您所见,它通过初始化程序获取一些 x 和 y 值,然后从中设置自己的 x 和 y。问题是,这个函数将从一个分配它们的数组的函数中调用:

BattleShip::BattleShip(BattlePoint start, enum shipTypeSize size, enum shipOrientation orientation) : size(size), orientation(orientation) {
    points = new BattlePoint[size]; // Here be doubts.
}

所以,我需要我的 BattleShip 点来保存一系列 BattlePoints,每个 BattlePoints 都有不同的初始化值(例如 0,1;0,2;0,3,等等)。

问题是:我如何分配未初始化的内存?

朱利安,

P.S.:我还没有对new 的工作方式进行任何测试,我只是简单地阅读了Wikipedia's article on it,上面写着:

在 C++ 编程语言中,以及在许多基于 C++ 的 语言,new 是一种动态分配的语言结构 堆上的内存,使用 构造函数。除了一种叫做“安置新”的形式,新 尝试在堆上为新数据分配足够的内存。如果 成功,它会初始化内存并将地址返回给 新分配和初始化的内存。但是,如果 new 无法分配 堆上的内存它将抛出 std::bad_alloc 类型的异常。 这消除了显式检查分配结果的需要。 调用delete,调用析构函数并返回内存 由 new 分配回堆,必须在每次调用 new 时进行 以避免内存泄漏。

placement new 应该是解决方案,但它没有提到如何去做。

附: 2:我知道这可以通过stdlib的vector类来完成,但我故意避开它。

【问题讨论】:

  • 什么会避免你使用 malloc?
  • 我正在使用 C++,这让我想使用特定于语言的解决方案。 malloc 是 C 的。
  • 如果你想使用严格的语言特性也没关系。但是,malloc 也是 C++ 的一部分,很像 new 运算符。正如您在 cmets 中所述,您希望避免使用 stdlib 的向量和默认构造函数。那么,为什么不使用简单的强制转换和 malloc?
  • 你有什么理由避免std::vector

标签: c++ memory new-operator


【解决方案1】:

您需要使用std::vector。在这种情况下,您可以push_back 任何您想要的,例如

std::vector<BattlePoint> x;
x.push_back(BattlePoint(1, 2));

如果您曾经发现自己在使用new[]deletedelete[],请立即重构您的程序以删除它们。它们在几乎所有可以想象的方面都非常不安全。而是使用资源管理类,例如 std::unique_ptrstd::vectorstd::shared_ptr

常规new 可以在涉及unique_ptr 的某些情况下很有用,但要避免它。此外,安置新的通常是不值得的。当然,如果您正在编写一个资源管理类,那么您可能必须将它们用作底层原语,但这很少而且相差甚远。

编辑:我的错误,我没有看到你问题的最后一行。解决它:

附: 2:我知道这可以通过 stdlib 的矢量类来完成,但我 故意避开它。

如果您有针对标准库的活动,请自行更换vector。但是不要没有vector 类。它必须由所有符合标准的编译器提供是有原因的。

【讨论】:

  • 它们为什么这么不安全?,同样的内存管理模型已经被C程序员使用了很长时间,尽管它带来了问题,但似乎它确实有效......跨度>
  • @Julian:C 没有例外。即便如此,在 C 中管理内存也是一件非常麻烦的事情。
  • @Julian:不仅存在异常问题,而且 C++ 的安全标准要高得多。通常,应保证 C++ 程序在编译时不会泄漏或重复删除,这比使用内存管理类更可能实现。这比 C.vector 的“好吧,我猜这次它成功了......”的标准要高得多从泄漏和双重删除。
  • @Julian 是的,效果很好。不知道为什么人们会认为编写 valgrind 是值得的;)
【解决方案2】:

points = new BattlePoint[size]; // 这里有疑问。
附言2:我知道这可以通过stdlib的vector类来完成,但我故意避开它。

肯定会有疑问!使用std::vector。你为什么不呢? 没有理由不使用std::vector尤其是如果它能解决您的问题。

std::vector<BattlePoint> bpoints;
bpoints.reserve(size); // there, only alloc'd memory, not initialized it.
bpoints.push_back(some_point); // still need to use push_back to initialize it

我相信问题会来——std::vector 是如何只分配内存的?! operator new 就是答案。当您使用new 时,会调用该运算符进行内存分配。 new 用于构造和初始化,而operator new 用于分配(这就是您可以重载它的原因)。

BattlePoint* bpoints = ::operator new(size); // happens in reserve
new (bpoints[index]) BattlePoint(some_x, some_y); // happens in push_back

【讨论】:

    【解决方案3】:

    为了回应上述答案,我肯定会向您指出std::vector,因为它是最好的解决方案。在C++ 中管理您自己的动态数组几乎从来都不是一个好主意,而且几乎从来没有必要。

    但是,要回答直接问题——在这种情况下,您可以创建一个默认构造函数和一些修改器来获得所需的效果:

    class BattlePoint {
    public:
        // default constructor, default initialize to 0,0
        BattlePoint() x(0), y(0) {};
    
        BattlePoint(int x, int y) : x(x), y(y) { };
        bool operator==(const BattlePoint &right);
        virtual ~BattlePoint();
    
        // mutator functions allow you to modify the classes member values
        void set_x(int x_) {x = x_;}
        void set_y(int y_) {y = y_;}
    
    private:
        int x, y;
    };
    

    然后你可以像在C 中习惯的那样初始化它:

    BattlePoint* points = new BattlePoint[100];
    
    for(int x = 0; x < 100; ++x)
    {
       points->set_x(x);
       points->set_y(x * 2);
    }
    

    如果您对基本上使 BattlePoint 类公开可变而感到困扰,您可以将 mutators 保持为私有并引入一个专门用于初始化值的友元函数。这是一个稍微复杂的概念,所以我暂时放弃对此的进一步解释,除非需要。

    既然你问了:)

    使用默认构造函数和修改器再次创建您的 BattlePoint 类,但是这次将修改器保留为私有,并声明一个友元函数来使用它们:

    class BattlePoint {
    public:
        // default constructor, default initialize to 0,0
        BattlePoint() x(0), y(0) {};
    
        BattlePoint(int x, int y) : x(x), y(y) { };
        bool operator==(const BattlePoint &right);
        virtual ~BattlePoint();
    
    private:
        // mutator functions allow you to modify the classes member values
        void set_x(int x_) {x = x_;}
        void set_y(int y_) {y = y_;}
    
        int x, y;
    
        friend void do_initialize_x_y(BattlePoint*, int, int);
    };
    

    创建一个包含用于创建BattlePoint 对象数组的本地函数的头文件。包含标题的任何人都可以使用此功能,但如果命名正确,那么“每个人”都应该知道不要使用它。

    // BattlePoint_Initialize.h
    BattlePoint* create_battle_point_array(size_t count, int* x, int* y);
    

    这个函数被定义在实现文件中,连同我们将对外界“隐藏”的友元函数:

    // BattlePoint_Initialize.cpp
    #include <BattlePoint_Initialize.h>
    
    namespace
    {
        // by putting this function in an anonymous namespace it is only available
        // to this compilation unit.  This function can only be called from within
        // this particular file.
        //
        // technically, the symbols are still exported, but they are mangled badly
        // so someone could call this, but they would have to really try to do it
        // not something that could be done "by accident"
        void do_initialize_x_y(BattlePoint* bp, int x, int y)
        {
            bp->set_x(x);
            bp->set_y(y);
        }
    }
    
    // caution, relies on the assumption that count indicates the number of
    // BattlePoint objects to be created, as well as the number of valid entries
    // in the x and y arrays
    BattlePoint* create_battle_point_array(size_t count, int* x, int* y)
    {
        BattlePoint* bp_array = new BattlePoint[count];
    
        for(size_t curr = 0; curr < count; ++curr)
        {
            do_initialize_x_y(bp_array[curr], x[curr], y[curr]);
        }
    
        return bp_array;
    }
    

    所以你有它。一种非常复杂的方式来满足您的基本要求。

    虽然create_battlepoint_array() 理论上可以在任何地方调用,但它实际上无法修改已经创建的BattlePoint 对象。 do_initialize_x_y() 函数本质上隐藏在隐藏在初始化代码后面的匿名 namespace 中,不能轻易地从程序中的其他任何地方调用。实际上,一旦创建了BattlePoint 对象(并分两步初始化),就无法进一步修改。

    【讨论】:

    • 是的,但这意味着有额外的功能并允许任何代码在初始化后修改我的 xs 和 ys,这是我试图避免的。
    • 额外功能,是的,允许任何代码修改 X,Y 值,否。新函数可以是初始化BattlePoint 对象的编译单元私有的。好奇你为什么要避开std::vector&lt;&gt;
    • 我想以最困难的方式进行编码,以便了解最难的方式,并了解语言在此过程中的工作方式。从那里开始让事情变得更容易并不难。顺便说一句,我将如何将它们设为仅对初始化它的人私有?
    • 谢谢!,确实比较难。我想我会去阅读有关朋友功能的信息,这似乎是一个有趣的概念。不能给你任何代表(或者至少我不知道如何),所以我只是投了赞成票。
    【解决方案4】:

    comp.lang.c++ FAQ 在这件事上有一些有用的话要说,包括试图劝阻你不要使用新位置 - 但如果你真的坚持,它确实有一个关于 placement new 及其所有陷阱的有用部分。

    【讨论】:

      猜你喜欢
      • 2018-02-18
      • 1970-01-01
      • 2015-11-08
      • 2012-01-30
      • 1970-01-01
      • 2013-12-27
      • 2012-12-04
      • 1970-01-01
      相关资源
      最近更新 更多