正确的内存管理方式

时间:2016-12-23 18:57:24

标签: c++ memory-management

class Word
{
private:
    std::string w;
public:
    Word(std::string w) : w(w) {}
};

class Sentence
{
private:
    std::list<Word *> words;

public:        
    Sentence(std::list<Word *> words) : words(words) {}

    ~Sentence() {
        for (auto word : words)
            delete word;
        words.clear();
    }
};

int main() {
    Word *word1 = new Word("abc");
    Word *word2 = new Word("def");
    Sentence sentence1( std::list<Word *>({word1, word2}) );
    Sentence sentence2 = sentence1;
    return 0;
}

(实际的类更大;我真的需要使用指向Word的指针,在其他函数中分配) 对象word1word2将被删除两次。我有解决问题的这些选项:

  1. 添加方法Word * Word::clone(const Word *word)Sentence * Sentence::clone(const Sentence *s),并在Sencence::operator=中调用它们。但在这种情况下,我的程序使用了太多的内存(句子中的单词相同,但在内存中分配了两次)。
  2. 使用智能指针。然后我的程序效率降低(代码变得更复杂)。
  3. bool isCopy添加到Sentence并仅在isCopy == false时删除字词。我认为这看起来很愚蠢。
  4. 那我应该做些什么呢?

3 个答案:

答案 0 :(得分:2)

永远不要使用指针集合。如果必须具有堆分配的word实现,请将其包装在具有类似行为的句柄类中。

然后将其存储在列表(或矢量等)

e.g:

struct word_impl {};   // big thing

auto clone(const word_impl& impl) -> std::unique_ptr<word_impl> {
    // clone could if you wished, defer to a virtual clone method
    // on word_impl
    return std::make_unique<word_impl>(impl);
}


struct word
{
     // construct from letters
     word(const char* letters) : impl_ { std::make_unique<word_impl>(letters) } { }

     // let's make it copyable
     word(const word& r) : impl_ { clone(r.impl_) } {}

     word& operator=(const word& r) {
         if (this != std::addressof(r)) {
             impl_ = clone(r.impl_);
         }
         return *this;
     }  

     // and moveable
     word(word&& r) = default;
     word& operator=(word&& r) = default;

private:

     std::unique_ptr<word_impl> impl_;
}

这是一个完整的,可编译的示例,用共享和可复制的单词表示单词object。

我在这里尝试做的是分离&#34;共享&#34;以及对单词的实际操作。

这允许您使用客户端站点上的相同代码来获得唯一单词或共享单词的句子。用户不需要知道单词的内部工作,甚至不知道如何管理其内存。他需要知道的是,它可以被复制,打印和比较(在这种情况下)。

还有一项操作可将unique_word转换为shared_word。

#include <memory>
#include <iostream>
#include <vector>
#include <iomanip>
#include <boost/operators.hpp>

//
// define some protocols that a handle class can inherit from. These
// translate methods on the handle onto methods on the implementation
//
template<class Handle>
struct implement_ostream_protocol {
    friend std::ostream &operator<<(std::ostream &os, const implement_ostream_protocol &proto) {
        auto &ref = static_cast<const Handle &>(proto).get_reference();
        return os << ref;
    }
};

template<class Handle, class Comp = std::less<>>
struct implement_less_than_protocol {
    friend bool operator<(const implement_less_than_protocol &l, const implement_less_than_protocol &r) {
        auto &lr = static_cast<const Handle &>(l).get_reference();
        auto &rr = static_cast<const Handle &>(r).get_reference();
        auto comp = Comp();
        return comp(lr, rr);
    }
};

template<class Handle>
struct implement_setValue_protocol {
    template<class T>
    decltype(auto) setValue(T&& value)
    {
        auto &lr = static_cast<Handle &>(*this).get_reference();
        return lr.setValue(std::forward<T>(value));
    }
};

//
// this is the implementation of a word
//
struct word_impl {
    word_impl(const char *letters) : word_(letters) {
        std::cout << "constructed word: " << word_ << std::endl;
    }

    word_impl(const word_impl &r) : word_(r.word_) {
        std::cout << "copied word: " << word_ << std::endl;
    }

    word_impl(word_impl &&r) noexcept : word_(std::move(r.word_)) {
        std::cout << "moved word: " << word_ << std::endl;
    }

    word_impl &operator=(const word_impl &r) {
        if (this != std::addressof(r)) {
            word_ = r.word_;
            std::cout << "assigned word: " << word_ << std::endl;
        }
        return *this;
    }

    word_impl &operator=(word_impl &&r) noexcept {
        if (this != std::addressof(r)) {
            word_ = std::move(r.word_);
            std::cout << "move-assigned word: " << word_ << std::endl;
        }
        return *this;
    }

    // some wordy operations
    bool comes_before(const word_impl &r) const {
        return word_ < r.word_;
    }

    void setValue(const char* p)
    {
        std::cout << "value changed from " << word_ << " to " << p << "\n";
        word_ = p;
    }

    // write myself
    friend std::ostream &operator<<(std::ostream &os, const word_impl &r) {
        return os << std::quoted(r.word_);
    }

    struct comes_before_op {
        bool operator()(const word_impl &l, const word_impl &r) const {
            return l.word_ < r.word_;
        }
    };

    std::string word_;
};   // big thing

//
// these are the protocols I want all types of word handles to support
//
template<class Handle>
struct word_impl_protocols
        : implement_ostream_protocol<Handle>,
          implement_less_than_protocol<Handle, word_impl::comes_before_op> ,
          implement_setValue_protocol<Handle>,
          boost::less_than_comparable<word_impl_protocols<Handle>>
{

};


auto clone(const word_impl &impl) -> std::unique_ptr<word_impl> {
    // clone could if you wished, defer to a virtual clone method
    // on word_impl
    return std::make_unique<word_impl>(impl);
}


//
// lets make a copyable word that clones its implementation
//
struct unique_word
        : word_impl_protocols<unique_word> {
    // construct from letters
    unique_word(const char *letters) : impl_{std::make_unique<word_impl>(letters)} {}

    // let's make it copyable
    unique_word(const unique_word &r) : impl_{clone(*r.impl_)} {}

    unique_word &operator=(const unique_word &r) {
        if (this != std::addressof(r)) {
            impl_ = clone(*r.impl_);
        }
        return *this;
    }

    // and moveable
    unique_word(unique_word &&r) noexcept = default;

    unique_word &operator=(unique_word &&r) noexcept = default;

    word_impl const &get_reference() const {
        return *impl_;
    }

    word_impl &get_reference() {
        return *impl_;
    }

    // warning - destructive - provides a means to create a
    // shared word from a unique_word
    auto share() {
        return std::shared_ptr<word_impl> {std::move(impl_)};
    }

private:

    std::unique_ptr<word_impl> impl_;
};

//
// and a word type that shares its implementation
//
struct shared_word
        : word_impl_protocols<shared_word> {
    shared_word(const char *letters) : impl_{std::make_shared<word_impl>(letters)} {}

    shared_word(unique_word &&source) : impl_{source.share()} {}

    const word_impl &get_reference() const { return *impl_; }
    word_impl &get_reference() { return *impl_; }

    std::shared_ptr<word_impl> impl_;
};


int main() {

    std::cout << "creating first sentence:\n";
    std::vector<unique_word> sentence1 = [] {
        std::vector<unique_word> result;
        result.emplace_back("abc");
        result.emplace_back("def");
        result.emplace_back("ghi");
        return result;
    }();

    std::cout << "copying first sentence:\n";
    std::vector<unique_word> sentence2 = sentence1;
    std::sort(sentence2.begin(), sentence2.end(), std::greater<>());

    std::copy(sentence1.begin(), sentence1.end(), std::ostream_iterator<unique_word>(std::cout, ", "));
    std::cout << std::endl;

    std::copy(sentence2.begin(), sentence2.end(), std::ostream_iterator<unique_word>(std::cout, ", "));
    std::cout << std::endl;

    std::cout << "converting first sentence to shared words:\n";
    std::vector<shared_word> sentence3;
    for (auto& unique : sentence1)
    {
        sentence3.emplace_back(std::move(unique));
    }
    std::copy(sentence3.begin(), sentence3.end(), std::ostream_iterator<shared_word>(std::cout, ", "));
    std::cout << std::endl;

    std::cout << "copying sentence of shared words:\n";
    auto sentence4 = sentence3;

    std::cout << "changing the first word of a shared word sentence:\n";
    sentence3.at(0).setValue("xyz");
    std::copy(sentence3.begin(), sentence3.end(), std::ostream_iterator<shared_word>(std::cout, ", "));
    std::cout << std::endl;
    std::copy(sentence4.begin(), sentence4.end(), std::ostream_iterator<shared_word>(std::cout, ", "));
    std::cout << std::endl;
}

预期产出:

creating first sentence:
constructed word: abc
constructed word: def
constructed word: ghi
copying first sentence:
copied word: abc
copied word: def
copied word: ghi
"abc", "def", "ghi", 
"ghi", "def", "abc", 
converting first sentence to shared words:
"abc", "def", "ghi", 
copying sentence of shared words:
changing the first word of a shared word sentence:
value changed from abc to xyz
"xyz", "def", "ghi", 
"xyz", "def", "ghi", 

答案 1 :(得分:1)

使用shared_ptr<>。不是因为它是最快的,因为这就是你在概念上的意思。事实证明这是正确的。只有当你确定它与程序相比较慢时才开始这种低级别的优化(提示:它不是)。

&LT; 1.&GT;表示您不共享您在概念上可能想要的实例。如果你改变其中一个词怎么办?

&LT; 3.&GT;意味着你不是线程安全的。好的,你添加一个互斥体 - 然后你最终会实现一个你必须支持的DIY fabric.Object,这很可能会更慢/更容易出错(考虑用户数量 - 测试人员) ! - 官方的)。不值得努力。

答案 2 :(得分:0)

这取决于你想要实现的目标。

如果您不想独立更改每个Sentence副本中的单词,那么使用智能指针的解决方案看起来不错。

使用深层复制的解决方案也很好,它是您想要的,即您希望单独复制每个单词(因此您可以单独更改它们)。但是,您应该考虑使用复制构造函数(或至少删除它)。

使用bool的解决方案看起来对我来说非常糟糕,因为它意味着第一个实例必须在它的副本之后销毁。但是,如果用一些共享的refcounter(std :: shared_ptr)替换bool,它也是可以接受的,但只能假设这些对象在创建后完全不可变。