new int [size] vs std :: vector [英] new int[size] vs std::vector

查看:115
本文介绍了new int [size] vs std :: vector的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了分配动态内存,我一直在C ++中使用向量。
但是最近,在阅读一些源代码时,我发现使用new int [size]和一些研究,发现它也分配了动态内存。



任何人都可以给我建议哪些更好?我从算法和ICPC的角度看?

解决方案

总是喜欢标准容器。



当你手动分配时,你必须保证释放代码被执行,并且作为成员,您必须编写正确的副本分配和副本构造函数,它在正常情况下不会泄漏。



手动:

  int * i = 0,* y = 0; 
try {
i = new int [64];
y = new int [64];
} catch(...){
delete [] y;
delete [] i;
}

如果我们想让变量只有他们真正需要的范围, smelly:

  int * i = 0,* y = 0; 
try {
i = new int [64];
y = new int [64];
//使用i和y的代码
int * p;
try {
p = new int [64];
//使用p,i,y的代码
} catch(...){}
delete [] p;
} catch(...){}
delete [] y;
delete [] i;

或只是:

  std :: vector< int> i(64),y(64); 
{
std :: vector< int> p(64);
}

对于具有复制语义的类来说,这是一个恐怖。复制可能抛出,分配可能抛出,我们想要事务语义,理想。



h1>可复制类 - 手动资源管理与容器

我们有这个无辜的类。事实证明,这是相当邪恶的。我想起美国麦吉的爱丽丝:

  class Foo {
public:
Foo (new Bar [64]),f_(new Frob [64]){}
private:
Bar * b_;
Frob * f_;
};

泄漏。大多数初学C ++程序员认识到有丢失的删除。添加他们:

  class Foo {
public:
Foo():b_ ]),f_(new Frob [64]){}
〜Foo(){delete f_;删除b_; }
private:
Bar * b_;
Frob * f_;
};

未定义的行为。中间C ++程序员认识到使用了错误的delete-operator。修正此问题:

  class Foo {
public:
Foo():b_ ]),f_(new Frob [64]){}
〜Foo(){delete [] f_; delete [] b_; }
private:
Bar * b_;
Frob * f_;
};

错误的设计,泄漏和双重删除,如果类被复制的话。复制本身很好,编译器干净地复制我们的指针。但是编译器不会发出代码来创建数组的副本。



稍微有些经验的C ++程序员认识到三原则尊重的,它说如果你明确写任何析构函数,复制赋值或复制构造函数,你可能还需要写出其他人,或使它们私有而不实现:

  class Foo {
public:
Foo():b_(new Bar [64]),f_(new Frob [64]){}
〜Foo(){delete [] f_; delete [] b_; }

Foo(Foo const& f):b_(new Bar [64]),f_(new Frob [64])
{
* this = f;
}
Foo& operator =(Foo const& rhs){
std :: copy(rhs.b_,rhs.b_ + 64,b_);
std :: copy(rhs.f_,rhs.f_ + 64,f_);
return * this;
}
private:
Bar * b_;
Frob * f_;
};

正确。 ...如果你能保证永远不会耗尽内存,并且Bar,也不Frob可以失败复制。下一节开始有趣。



编写异常安全代码的奇迹。



b
$ b

  Foo():b_(new Bar [64]),f_(new Frob [64]){} 




  • 问:如果初始化 f _ 失败?

  • A:已经构建的所有 Frobs 想象20 Frob 被建造,第21次将失败。按照LIFO顺序,第一个20 Frob 将被正确销毁。



就是这样。意思是:现在你有64个僵尸 Bars Foos 对象本身永远不会生命,因此它的析构函数不会被调用。



如何使这个异常安全?



构造函数应始终成功完全或失败完全。它不应该是半死或半死。解决方案:

  Foo():b_(0),f_(0)
{
b_ = new Bar [64]
f_ = new Foo [64];
} catch(std :: exception& e){
delete [] f_; //注意:删除空指针是安全的 - >没有发生
delete [] b_;
throw; //不要忘记中止这个对象,不要让它生命
}
}



复制



记住我们的复制定义:

  Foo(Foo const& f):b_(new Bar [64]),f_(new Frob [64]){
* this = f;
}
Foo& operator =(Foo const& rhs){
std :: copy(rhs.b_,rhs.b_ + 64,b_);
std :: copy(rhs.f_,rhs.f_ + 64,f_);
return * this;
}




  • 问:如果任何复制失败,会发生什么?可能 Bar 将不得不复制大量资源。

A:在异常的时间点,所有复制的对象将保持不变。


这意味着我们的 Foo 现在处于不一致和不可预测的状态。为了赋予它事务语义,我们需要完全建立新的状态或者根本不建立新的状态,然后使用不能抛出的操作来在我们的 Foo 中植入新的状态。最后,我们需要清除临时状态。



解决方案是使用复制和交换惯用法(http://gotw.ca/gotw/059.htm) )。



首先,我们改进我们的复制构造函数:

  Foo const& f):f_(0),b_(0){
try {
b_ = new Bar [64]
f_ = new Foo [64];

std :: copy(rhs.b_,rhs.b_ + 64,b_); //如果这抛出,所有提交的副本将被丢弃
std :: copy(rhs.f_,rhs.f_ + 64,f_);
} catch(std :: exception& e){
delete [] f_; //注意:删除空指针是安全的 - >没有发生
delete [] b_;
throw; //不要忘记中止这个对象,不要让它生命
}
}

然后,我们定义一个非抛出交换函数

  class Foo {
public:
friend void swap(Foo& amp; Foo&);
};

void swap(Foo& lhs,Foo& rhs){
std :: swap(lhs.f_,rhs.f_);
std :: swap(lhs.b_,rhs.b_);
}



现在我们可以使用新的异常安全拷贝构造函数和异常安全交换函数写一个异常安全的拷贝赋值运算符:

  Foo& operator =(Foo const& rhs){
Foo tmp(rhs); //如果这个抛出,一切都被释放,异常被传播
swap(tmp,* this); // can not throw
return * this; //不能throw
} // Foo ::〜Foo()被执行

发生了什么?首先,我们建立新的存储和复制rhs'。这可能会抛出,但如果是这样,我们的状态不会改变,对象仍然有效。



然后,我们用临时的内容交换我们的内容。临时获得不再需要的东西,并释放那些东西在范围的末端。我们有效地使用 tmp 作为垃圾桶,并正确选择RAII作为垃圾收集服务。



您可能需要查看 http://gotw.ca/gotw/059.htm 或阅读异常C ++



将它组合在一起



不能抛出或不允许抛出:




  • 复制基本类型不会抛出

  • 析构函数不允许抛出(因为否则,异常安全代码根本不可能实现)

  • strong>函数不应抛弃**(和C ++程序员以及整个标准库期望它不抛出)



是最终我们精心制作的,异常安全的,更正版本的Foo:

  class Foo {
public:
Foo():b_(0),f_(0)
{
try {
b_ = new Bar [64]
f_ = new Foo [64];
} catch(std :: exception& e){
delete [] f_; //注意:删除空指针是安全的 - >没有发生
delete [] b_;
throw; //不要忘记中止这个对象,不要让它生存
}
}

Foo(Foo const& f):f_(0), b_(0)
{
try {
b_ = new Bar [64];
f_ = new Foo [64];

std :: copy(rhs.b_,rhs.b_ + 64,b_);
std :: copy(rhs.f_,rhs.f_ + 64,f_);
} catch(std :: exception& e){
delete [] f_;
delete [] b_;
throw;
}
}

〜Foo()
{
delete [] f_;
delete [] b_;
}

Foo& operator =(Foo const& rhs)
{
Foo tmp(rhs); //如果这个抛出,一切都被释放,异常被传播
swap(tmp,* this); // can not throw
return * this; //不能throw
} // Foo ::〜Foo()被执行

friend void swap(Foo& amp; amp; amp;

private:
Bar * b_;
Frob * f_;
};

void swap(Foo& lhs,Foo& rhs){
std :: swap(lhs.f_,rhs.f_);
std :: swap(lhs.b_,rhs.b_);
}

与我们最初的无辜的代码相比,这是邪恶的骨头:

  class Foo {
public:
Foo():b_(new Bar [64] f_(new Frob [64]){}
private:
Bar * b_;
Frob * f_;
};

你最好不要添加更多的变量。迟早,你会忘记在某个地方添加正确的代码,而你的整个班级都会生病。



或使其不可复制。



  class Foo {
public:
Foo():b_(new Bar [64]),f_(new Frob [64 ]){}
Foo(Foo const&)= delete;
Foo& operator =(Foo const&)= delete;
private:
Bar * b_;
Frob * f_;
};

对于某些类,这是有意义的(流,对于实例;要共享流, :: shared_ptr),但对很多人来说,它不会。



真正的解决方案。



  class Foo {
public:
Foo():b_(64),f_(64){}
private:
std :: vector< Bar> ; b_;
std :: vector< Frob> F_;
};

这个类有干净的复制语义,是异常安全的(记住:异常安全并不意味着throw,而不是泄漏,可能有事务语义),并且不泄漏。


To allocate dynamic memory, I have been using vectors all this time in C++. But recently, while reading some source code, I found the use of "new int[size]" and on some research, found that it too allocates dynamic memory.

Can anyone give me advice as to which is better? I am looking from an algorithmic and ICPC point of view?

解决方案

Always prefer the standard containers. They have well-defined copying semantics, are exception safe, and release properly.

When you allocate manually, you must guarantee that the release code is executed, and as members, you must write correct copy assignment and copy constructor which does the right thing without leaking in case of exception.

Manual:

int *i = 0, *y = 0;
try {
    i = new int [64];
    y = new int [64];
} catch (...) {
    delete [] y;
    delete [] i;
}

If we want our variables to have only the scope they really need, it gets smelly:

int *i = 0, *y = 0;
try {
    i = new int [64];
    y = new int [64];
    // code that uses i and y
    int *p;
    try { 
        p = new int [64];
        // code that uses p, i, y
    } catch(...) {}
    delete [] p;
} catch (...) {}
delete [] y;
delete [] i;

Or just:

std::vector<int> i(64), y(64);
{
    std::vector<int> p(64);
}

It is a horror to implement that for a class that has copy semantics. Copying may throw, allocation may throw, and we want transaction semantics, ideally. An example would burst this answer tho.


Ok here.

Copyable class - Manual resource management vs containers

We have this innocent looking class. As it turns out, it is pretty evil. I feel reminded of American McGee's Alice:

class Foo {
public:
    Foo() : b_(new Bar[64]), f_(new Frob[64]) {}
private:
    Bar  *b_;
    Frob *f_;
};

Leaks. Most beginner C++ programmers recognize that there's missing deletes. Add them:

class Foo {
public:
    Foo() : b_(new Bar[64]), f_(new Frob[64]) {}
    ~Foo() { delete f_; delete b_; }
private:
    Bar  *b_;
    Frob *f_;
};

Undefined behavior. Intermediate C++ programmers recognize that the wrong delete-operator is used. Fix this:

class Foo {
public:
    Foo() : b_(new Bar[64]), f_(new Frob[64]) {}
    ~Foo() { delete [] f_; delete [] b_; }
private:
    Bar  *b_;
    Frob *f_;
};

Bad design, leaks and double deletes lurk there if the class is copied. The copying itself is fine, the compiler cleanly copies the pointers for us. But the compiler won't emit code to create copies of the arrays.

Slightly more experienced C++ programmers recognize that the Rule of Three was not respected, which says that if you have explicitly written any of destructor, copy assignment or copy constructor, you probably also need to write out the others, or make them private without implementation:

class Foo {
public:
    Foo() : b_(new Bar[64]), f_(new Frob[64]) {}
    ~Foo() { delete [] f_; delete [] b_; }

    Foo (Foo const &f) : b_(new Bar[64]), f_(new Frob[64])
    {
        *this = f;
    }
    Foo& operator= (Foo const& rhs) {
        std::copy (rhs.b_, rhs.b_+64, b_);
        std::copy (rhs.f_, rhs.f_+64, f_);
        return *this;
    }
private:
    Bar  *b_;
    Frob *f_;
};

Correct. ... Provided you can guarantee to never run out of memory and that neither Bar, nor Frob can fail on copying. Fun starts in next section.

The wonderland of writing exception safe code.

Construction

Foo() : b_(new Bar[64]), f_(new Frob[64]) {}

  • Q: What happens if the initialization of f_ fails?
  • A: All Frobs that have been constructed are destroyed. Imagine 20 Frob were constructed, the 21st will fail. Than, in LIFO order, the first 20 Frob will be correctly destroyed.

That's it. Means: You have 64 zombie Bars now. The Foos object itself never comes to life, its destructor will therefore not be called.

How to make this exception safe?

A constructor should always succeed completely or fail completely. It shouldn't be half-live or half-dead. Solution:

Foo() : b_(0), f_(0)
{
    try {    
        b_ = new Bar[64];
        f_ = new Foo[64];
    } catch (std::exception &e) {
        delete [] f_; // Note: it is safe to delete null-pointers -> nothing happens
        delete [] b_;
        throw; // don't forget to abort this object, do not let it come to life
    }
}

Copying

Remember our definitions for copying:

Foo (Foo const &f) : b_(new Bar[64]), f_(new Frob[64]) {
    *this = f;
}
Foo& operator= (Foo const& rhs) {
    std::copy (rhs.b_, rhs.b_+64, b_);
    std::copy (rhs.f_, rhs.f_+64, f_);
    return *this;
}

  • Q: What happens if any copy fails? Maybe Bar will have to copy heavy resources under the hood. It can fail, it will.
  • A: At the time-point of exception, all objects copied so far will remain like that.

This means that our Foo is now in inconsistent and unpredictable state. To give it transaction semantics, we need to build up the new state fully or not at all, and then use operations that cannot throw to implant the new state in our Foo. Finally, we need to clean up the interim state.

The solution is to use the copy and swap idiom (http://gotw.ca/gotw/059.htm).

First, we refine our copy constructor:

Foo (Foo const &f) : f_(0), b_(0) { 
    try {    
        b_ = new Bar[64];
        f_ = new Foo[64];

        std::copy (rhs.b_, rhs.b_+64, b_); // if this throws, all commited copies will be thrown away
        std::copy (rhs.f_, rhs.f_+64, f_);
    } catch (std::exception &e) {
        delete [] f_; // Note: it is safe to delete null-pointers -> nothing happens
        delete [] b_;
        throw; // don't forget to abort this object, do not let it come to life
    }
}

Then, we define a non-throwing swap function

class Foo {
public:
    friend void swap (Foo &, Foo &);
};

void swap (Foo &lhs, Foo &rhs) {
    std::swap (lhs.f_, rhs.f_);
    std::swap (lhs.b_, rhs.b_);
}

Now we can use our new exception safe copy-constructor and exception-safe swap-function to write an exception-safe copy-assignment-operator:

Foo& operator= (Foo const &rhs) {
    Foo tmp (rhs);     // if this throws, everything is released and exception is propagated
    swap (tmp, *this); // cannot throw
    return *this;      // cannot throw
} // Foo::~Foo() is executed

What happened? At first, we build up new storage and copy rhs' into it. This may throw, but if it does, our state is not altered and the object remains valid.

Then, we exchange our guts with the temporary's guts. The temporary gets what is not needed anymore, and releases that stuff at the end of scope. We effectively used tmp as a garbage can, and properly choose RAII as the garbage collection service.

You may want to look at http://gotw.ca/gotw/059.htm or read Exceptional C++ for more details on this technique and on writing exception safe code.

Putting it together

Summary of what can't throw or is not allowed to throw:

  • copying primitive types never throws
  • destructors are not allowed to throw (because otherwise, exception safe code would not be possible at all)
  • swap functions shall not throw** (and C++ programmers as well as the whole standard library expect it to not throw)

And here is finally our carefully crafted, exception safe, corrected version of Foo:

class Foo {
public:
    Foo() : b_(0), f_(0)
    {
        try {    
            b_ = new Bar[64];
            f_ = new Foo[64];
        } catch (std::exception &e) {
            delete [] f_; // Note: it is safe to delete null-pointers -> nothing happens
            delete [] b_;
            throw; // don't forget to abort this object, do not let it come to life
        }
    }

    Foo (Foo const &f) : f_(0), b_(0)
    { 
        try {    
            b_ = new Bar[64];
            f_ = new Foo[64];

            std::copy (rhs.b_, rhs.b_+64, b_);
            std::copy (rhs.f_, rhs.f_+64, f_);
        } catch (std::exception &e) {
            delete [] f_;
            delete [] b_;
            throw;
        }
    }

    ~Foo()
    {
        delete [] f_;
        delete [] b_;
    }

    Foo& operator= (Foo const &rhs)
    {
        Foo tmp (rhs);     // if this throws, everything is released and exception is propagated
        swap (tmp, *this); // cannot throw
        return *this;      // cannot throw
    }                      // Foo::~Foo() is executed

    friend void swap (Foo &, Foo &);

private:
    Bar  *b_;
    Frob *f_;
};

void swap (Foo &lhs, Foo &rhs) {
    std::swap (lhs.f_, rhs.f_);
    std::swap (lhs.b_, rhs.b_);
}

Compare that to our initial, innocent looking code that is evil to the bones:

class Foo {
public:
    Foo() : b_(new Bar[64]), f_(new Frob[64]) {}
private:
    Bar  *b_;
    Frob *f_;
};

You better don't add more variables to it. Sooner or later, you will forget to add proper code at some place, and your whole class becomes ill.

Or make it non-copyable.

class Foo {
public:
    Foo() : b_(new Bar[64]), f_(new Frob[64]) {}
    Foo (Foo const &) = delete;
    Foo& operator= (Foo const &) = delete;
private:
    Bar  *b_;
    Frob *f_;
};

For some classes this makes sense (streams, for an instance; to share streams, be explicit with std::shared_ptr), but for many, it does not.

The real solution.

class Foo {
public:
    Foo() : b_(64), f_(64) {}
private:
    std::vector<Bar>  b_;
    std::vector<Frob> f_;
};

This class has clean copying semantics, is exception safe (remember: being exception safe does not mean to not throw, but rather to not leak and possibly have transaction semantics), and does not leak.

这篇关于new int [size] vs std :: vector的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆