防止临时变量延长其生命周期?

8

这可能是不可能的,但我想知道是否可以让临时变量永远保持在其原始表达式之后。我有一系列指向父对象的对象链,以及一个创建子对象的成员函数,以下是一个简化的示例:

class person{
    string name;
    person * mommy;
public:
    person(const string & nam, person * m = 0) : name(nam), mommy(m) {}
    person baby(const string & nam){
        return person(nam, this);
    }
    void talk() const{
        if (mommy) mommy->talk();
        cout << name << endl;
    }
};

int main(){
    person("Ann").baby("Susan").baby("Wendy").talk();     // fine

    const person & babygirl = person("Julie").baby("Laura"); // not fine

    babygirl.talk();    // segfault
    return 0;
}

我想使用 person 的方式是将其传递给函数,类似于这样的语法:
void use(const person & p) {
    p.talk();
}
use(person("Anna").baby("Lisa"));

很好。

只要临时变量没有在原始表达式之后存在,这个方法就可以正常工作,但是如果我将最终的临时变量绑定到一个const引用,它的父对象就不会存在,我会得到一个段错误。我可以隐藏person的复制构造函数和赋值运算符,但是有没有办法防止这种错误发生?如果可能的话,我想避免动态分配。


1
请注意,这段代码的问题与编写const int &i = std::vector<int>(1)[0];一样“不好”。vector不会阻止你编写这样的代码,也不需要。关键在于,因为摧毁“妈妈”会使“宝宝”无法使用,所以“宝宝”是“妈妈”的一部分。这就是设计上的问题,它是违反直觉的。你试图通过防止存在孤儿来修补这个问题,这可能是合适的,但你也应该考虑孤儿是否应该有更好定义的行为,或者是否应该更明显地成为一个不好的创建方式。 - Steve Jessop
同意Steve的观点:这只是糟糕的设计展示。裸指针的出现应该已经表明了问题所在。 - Kerrek SB
2个回答

3
看起来你正在创建一个数据结构,其中子项有指向它们的父项的指针。在这种情况下,使用临时变量保证会让你感到痛苦。为了使其安全,你需要动态分配并可能使用某种引用计数。
你是否考虑过使用boost::shared_ptr?它是一个实现引用计数智能指针类的库。使用shared_ptr和一些工厂方法,您可能能够获得您想要的效果,并减少动态内存分配的痛苦。我尝试过它,它似乎可以工作。一旦代码退出作用域,因为没有剩余的共享指针引用,所有对象都将被销毁。
编辑:针对zounds'的评论,我修改了示例,以便根对象控制数据结构的生命周期。
#include <iostream>
#include <string>
#include <vector>
#include <boost\shared_ptr.hpp>
#include <boost\weak_ptr.hpp>

using boost::shared_ptr;
using boost::weak_ptr;

using std::string;
using std::cout;
using std::endl;
using std::vector;

class person;
typedef shared_ptr<person> Person;
typedef weak_ptr<person> PersonWk;

class person {    
    PersonWk pThis;
    friend Person makePerson(const string & nam, Person m = Person());

    string name;
    PersonWk mommy; // children should not affect parent lifetime, so store weak ptr
    vector<Person> children; // parents affect children lifetime so store child shared ptrs

    // make constructor private so that you can only make a person using factory method
    person(const string & nam, Person m) : name(nam), mommy(m) 
    { 
        // for demo purposes
        printf("creating %s\n", nam.c_str());
        ++personCount; 
    }

    // undefined copy constructor and assignment operators
    person(const person&);
    person& operator=(const person&);

public:
    // for demo purposes
    static int personCount;

    ~person() 
    { 
        // for demo purposes
        printf("destroying %s\n", name.c_str());
        --personCount; 
    }

    Person baby(const string & nam){        
        Person child = makePerson(nam, Person(pThis));
        children.push_back(child);
        return child;
    }

    void talk() const{
        if (Person mom = mommy.lock()) 
            mom->talk();
        cout << name << endl;
    }
};

int person::personCount = 0;

// factory method to make a person
Person makePerson(const string & name, Person m) {
    Person p = Person(new person(name, m));
    p->pThis = p; // stash weak_ptr so I can use it to make a shared_ptr from "this" in the baby method
    return p;
}

void use(const Person p) {
    printf("In method use...\n");
    p->talk();
}

int _tmain(int argc, _TCHAR* argv[])
{
    printf("personCount=%d\n", person::personCount);
    {
        Person ann = makePerson("Ann");

        // ann has baby and grandbaby, pass grandbaby to use method
        use(ann->baby("Susan")->baby("Wendy"));

        ann.reset(); // remove reference from root object. Destruction ensues...
    }
    printf("personCount=%d\n", person::personCount);
    return 0;
}

谢谢你的回答。对我来说,使用shared ptr的问题在于根对象可能需要有一个特定的销毁点,而我不想担心它是否可能被存储在某个地方的指针所引用。我只需要临时对象在函数调用期间存在,然后它们就可以消失了。我正在使用子->父结构,其中子对象不允许影响其父对象(除了通过每个人都可以使用的正常公共接口)。 - zounds
一种方法是将根对象存储在已知位置。然后,每个父级都保持对其子级的shared_ptr,而子级可以保持对其父级的weak_ptr。然后,当您将根对象设置为NULL(从而删除所有引用)时,树将从顶部到底部被销毁。 - m-sharp
修改了代码示例,使用了我之前评论中提到的方法。 - m-sharp

0
你需要像这样做:
void use(const person & p) {
    p.talk();
}
person a("Anna");
use(a.baby("Lisa"));

这样,父级 "a" 直到你真正完成它(在调用 "use" 后)才会超出范围。

原始代码的问题在于,父级 "Anna" 只需要在调用 "baby" 之前保持足够长的时间,然后可以在函数调用之前丢弃父级。通过将父级作为具有作用域的变量,您可以控制何时销毁它。

这看起来对我来说危险吗?是的。因此,我建议查看 m-sharp 在动态分配方面的答案。但是,如果您想要一种不需要引用计数等方法的方法,则可以使用此方法...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接