通过返回的指针填充私有域

Fill private fields through returned pointers

我有一个容器class:

class Container {
public:
    MyItem* getItem() { return mItem.get(); }
private:
    std::auto_ptr<MyItem> mItem;
};

我正在尝试编写一个 initializeContainer() 方法,其中我使用 Container.getItem() 来设置 mItem。以下是一些可能的方法,我在评论中对它们表示怀疑:

void initialize(Conatiner& container) {
    MyItem item;
    container.getItem() = &item; // Wrong, item gets destroyed at the end of the function

    container.getItem() = new MyItem();          
    // This approach is filling my needs so far, but that doesn't necessarily
    // mean it's correct.
    // In particular, I'm not sure this approach properly removes the original item.

    // Here I try to use placement new to reuse the memory pointed to by container.getitem
    if (container.getItem()) {
        MyItem* pItem = container.getItem();
        pItem->~MyItem();
        pItem = new (pItem) MyItem();
    } // but if the pointer is null, I don't have any memory to reassign!
}

是否有惯用的方法来处理通过指针填充字段?我无权访问 C++11 功能或 Boost 等外部库。 Container.

的界面我也不允许改

下面隐含修改classContainer和非空指针

例如,使用普通指针(不是 auto)和一个 return 引用:

#include <iostream>

class MyItem{
private:
    int a;
public:
    ~MyItem(){std::cout<<"My item destroyed!\n";}
    MyItem(int _a):a(_a){};
    int getValue(){return a;}
};

class Container {
public:
    MyItem & getItem() { return *(mItem); }
private:
    MyItem* mItem;
};

void initialize(Container& container) {
    std::cout<<"New item\n";
    MyItem* p2=new MyItem(25);
    std::cout<<"Getting item\n";
    MyItem &p1 = container.getItem();
    std::cout<<"Copying\n";
    p1=*p2; // should define your own operator = for complex MyItem type
    delete p2;
    std::cout<<"New item\n";
    MyItem* p3=new MyItem(5);
    std::cout<<"Copying\n";
    p1=*p3;
    delete p3;
}

int main()
{
    Container c;
    initialize(c);
    std::cout<<c.getItem().getValue()<<std::endl;
    return 0;
}

输出:

New item
Getting item
Copying
My item destroyed!
New item
Copying
My item destroyed!
5

使用指针而不是引用(仅更改相关代码块):

    ...
    MyItem* getItem() { return mItem; }
    ...
    MyItem *p1 = container.getItem();
    std::cout<<"Copying\n";
    *p1=*p2;
    ... (the same with p3)
    ...
    std::cout<<c.getItem()->getValue()<<std::endl;

下面的答案使用C++11.

您可以通过引用访问unique_ptr,然后使用移动语义来 改变智能指针指向的对象的值。

一个例子

#include <iostream>
#include <memory>

class MyItem{
private:
    int a;
public:
    ~MyItem(){std::cout<<"My item destroyed!\n";}    
    MyItem(int _a):a(_a){};
    int getValue(){return a;}
};

class Container {
public:
    std::unique_ptr<MyItem> & getItem() { return mItem; }
private:
    std::unique_ptr<MyItem> mItem;
};

void initialize(Container& container) {
    std::unique_ptr<MyItem> p2(new MyItem(5));
    std::unique_ptr<MyItem> &p1 = container.getItem();
    std::cout<<"Copying\n";
    p1 = std::move(p2);
    std::unique_ptr<MyItem> p3(new MyItem(20));
    std::cout<<"Copying\n";
    p1 = std::move(p3);
}

int main()
{
    Container c;
    initialize(c);
    std::cout<<c.getItem()->getValue()<<std::endl;
    return 0;
}

initializeContainer() simply does not have access to set the Container::mItem member through normal channels. And you can't use Container::getItem() to provide that access, because it returns the MyItem* pointer that mItem holds, and you can't reach mItem from that pointer.

You need to change Container to allow access to mItem, either by:

  1. giving Container a public method that sets mItem, then have initializeContainer() call that method:

    class Container {
    private:
        std::auto_ptr<MyItem> mItem;
    public:
        MyItem* getItem() { return mItem.get(); }
        void setItem(const MyItem &item) { mItem.reset(new MyItem(item)); }
    };
    
    void initialize(Container& container) {
        MyItem item;
        container.setItem(item);
    }
    
  2. declaring initializeContainer() as a friend of Container so it can access private members directly:

    class Container {
    private:
        std::auto_ptr<MyItem> mItem;
    public:
        MyItem* getItem() { return mItem.get(); }
    
        friend void initialize(Container&);
    };
    
    void initialize(Container& container) {
        container.mItem.reset(new MyItem);
    }
    
  3. Getting rid of initializeContainer() altogether and give Container a public initialization method instead:

    class Container {
    private:
        std::auto_ptr<MyItem> mItem;
    public:
        void init() { mItem.reset(new MyItem); }
        MyItem* getItem() { return mItem.get(); }
    };
    
    Container c;
    c.init();
    

Is there an idiomatic way to handle populating fields through a pointer?

Not the way you are attempting to do it, no. You are trying to use a pointer that is not related to the Container object itself, just held by it. So you can't use that pointer to access members of the Container object, since it does not point at the Container object to begin with.

I am also not allowed to change the interface of Container.

Well, then you are out of luck, because what you are attempting to do requires an interface change. Unless you use an ugly pointer hack, eg:

class Container {
private:
    std::auto_ptr<MyItem> mItem;
public:
    MyItem* getItem() { return mItem.get(); }
};

void initialize(Container& container) {
    unsigned char *p = reinterpret_cast<unsigned char*>(&container);
    std::auto_ptr<MyItem> *ap = reinterpret_cast<std::auto_ptr<MyItem>*>(p + offsetof(Container, mItem));
    ap->reset(new MyItem);
}

On the other hand, if your intention is not to change mItem itself, but simply to (re-)initialize the MyItem object that mItem is already holding, you can use getItem() for that, but only if the MyItem object has been created beforehand:

void initialize(Container &container) {
    MyItem *item = container.getItem();
    if (item) *item = MyItem();
}

Which you can ensure by not allowing mItem to hold a null pointer in the first place:

class Container {
private:
    std::auto_ptr<MyItem> mItem;
public:
    Container() : mItem(new MyItem) {}
    Container(const Container &src) : mItem(new MyItem(src.getItem())) {}
    Container& operator=(const Container &rhs) { mItem.reset(new MyItem(rhs.getItem())); return *this; }

    MyItem& getItem() { return *mItem.get(); }
};