【问题标题】:Passing around an object in C++ by reference [duplicate]通过引用在 C++ 中传递对象 [重复]
【发布时间】:2013-07-28 05:23:56
【问题描述】:

我将对象放入std::vector。稍后我需要遍历向量并更改每个位置的对象中的一些成员变量。

我想一旦我拥有对象by reference 后,我想将它传递给一个对其进行操作的函数,但我似乎遇到了错误:

Non-const lvalue reference to type 'Object' cannot bind to a value of unrelated type 'Object *'

这里是省略代码的一般要点:

Object* o1 = Object::createWithLocation(p.x, p.y);
v.push_back(o1); 

// later on
for (int f=0; f < v.size(); f++)
{
    Object* obj1 = v.at(f);

    addChild(h->createLayer(obj1), 3); // add the HUD
}

createLayer 定义在:

static PlantingHUD* createLayer(Object &o);    

谁能解释我在指针和引用传递之间的混淆?我必须做某种演员吗?

【问题讨论】:

  • 您是否阅读了错误信息?您是否查看过函数参数的类型和实际传入的变量类型?太简单了……
  • 我做了,我的想法是使用 Object 而不是 Object* 但向量定义为:std::vector&lt;Object *&gt; v;
  • 初学者的 C++ 指南对将 Object * 转换为 Object 有什么看法?当然,您使用第一章(或第二章)中描述的指针取消引用运算符(*)...
  • 让我回去看看,感谢关于取消引用的提醒。我敢打赌,这就是我没有做到的。

标签: c++ compiler-errors parameter-passing


【解决方案1】:
static PlantingHUD* createLayer(Object &o);

这个方法需要一个Object的引用作为参数, 但你的输入是一个指针。

Object* obj1 = v.at(f);

addChild(h->createLayer(obj1), 3); // add the HUD

这就是问题所在。

【讨论】:

    【解决方案2】:
    void foo(Object o)
    

    声明一个函数 foo,它将从名为 'o' 的类 'Object' 的新实例开始执行。

    这称为“按值传递”,但更准确地说是“复制”,因为 foo 收到的是它自己的、我们称之为 foo 的 Object 实例的个人复制。当“foo”结束时,它所知道、被喂养和上学的“Object o”将不复存在。

    void foo(Object& o)
    

    声明一个函数 foo,它将以对“对象”的现有实例的引用开始执行,该引用将被称为“o”。如果你戳或戳它,你将改变原来的。

    这称为“通过引用传递”。

    void foo(Object* o)
    

    声明一个函数 foo,它将从一个名为“o”的变量开始执行,该变量包含应该是“Object”实例的地址。如果您更改此变量,通过执行“o = nullptr”之类的操作,它只会影响 foo 内部的外观。但是,如果您将塞缪尔·杰克逊 (Samuel L Jackson) 发送到该地址,他可以进行持续超过 foo 生命周期的愤怒报复。

    void foo(Object*& o)
    

    声明一个函数 foo,它将从一个名为“o”的变量开始执行,该变量是对指向对象 o 实例的指针的引用——它就像一个别名,除了没有编译器优化,它实际上是由编译器使用某种指针。

    让我们分别尝试这些。

    #include <iostream>
    #include <cstdint>
    
    struct Object
    {
        int m_i;
    
        void event(const char* what, const char* where)
        {
            std::cout <<
                what<< " " << (void*)this <<
                " value " << m_i <<
                " via " << where <<
                std::endl;
        }
    
        // Construct an object with a specific value.
        Object(int i) : m_i(i)
        {
            event("Constructed", "Operator(int i)");
        }
    
        // This is called the copy constructor, create one object from another.
        Object(const Object& rhs) : m_i(rhs.m_i)
        {
            event("Constructed", "Operator(const Object&)");
        }
    
        // This is how to handle Object o1, o2; o1 = o2;
        Object& operator=(const Object& rhs)
        {
            m_i = rhs.m_i;
            event("Assigned", "operator=");
            return *this;
        }
    
        // Handle destruction of an instance.
        ~Object() { event("Destructed", "~Object"); }
    };
    
    void foo1(Object o)
    {
        std::cout << "Entered foo1, my o has value " << o.m_i << std::endl;
        // poke our local o
        o.m_i += 42;
        std::cout << "I changed o.m_i, it is " << o.m_i << std::endl;
    }
    
    void foo2(Object* o)
    {
        std::cout << "Foo2 starts with a pointer, it's value is " << (uintptr_t)o << std::endl;
        std::cout << "That's an address: " << (void*)o << std::endl;
        std::cout << "m_i of o has the value " << o->m_i << std::endl;
        o->m_i += 42;
        std::cout << "I've changed it tho, now it's " << o->m_i << std::endl;
    }
    
    void foo3(Object& o)
    {
        std::cout << "foo3 begins with a reference called o, " << std::endl <<
            "which is sort of like a pointer but the compiler does some magic " << std::endl <<
            "and we can use it like a local concrete object. " <<
            std::endl <<
            "Right now o.m_i is " << o.m_i <<
            std::endl;
        o.m_i += 42;
        std::cout << "Only now, it is " << o.m_i << std::endl;
    }
    
    void foo4(Object*& o)
    {
        std::cout << "foo4 begins with a reference to a pointer, " << std::endl <<
            "the pointer has the value " << (uintptr_t)o << " which is " <<
            (void*)o <<
            std::endl <<
            "But the pointer points to an Object with m_i of " << o->m_i << std::endl <<
            "which we accessed with '->' because the reference is to a pointer, " <<
            "not to an Object." <<
            std::endl;
        o->m_i += 42;
        std::cout << "I poked o's m_i and now it is " << o->m_i << std::endl;
        // Now for something really dastardly.
        o = new Object(999);
        std::cout << "I just changed the local o to point to a new object, " <<
            (uintptr_t)o << " or " << (void*)o << " with m_i " << o->m_i <<
            std::endl;
    }
    
    int main()
    {
        std::cout << "Creating our first objects." << std::endl;
        Object o1(100), o2(200);
    
        std::cout << "Calling foo1 with o1" << std::endl;
        foo1(o1);
        std::cout << "back in main, o1.m_i is " << o1.m_i << std::endl;
    
        std::cout << "Calling foo2 with &o1" << std::endl;
        foo2(&o1);
        std::cout << "back in main, o1.m_i is " << o1.m_i << std::endl;
    
        std::cout << "Calling foo3(o2), which looks like the way we called foo1." << std::endl;
        foo3(o2);
        std::cout << "back in main, o2.m_i is " << o2.m_i << std::endl;
    
        std::cout << "Creating our pointer." << std::endl;
        Object* optr;
        std::cout << "Setting it to point to 'o2'" << std::endl;
        optr = &o2;
        std::cout << "optr now has the value " << (uintptr_t)optr <<
            " which is the address " << (void*)optr <<
            " which points to an Object with m_i = " << optr->m_i <<
           std::endl;
    
        foo4(optr);
    
        std::cout << "back in main, o2 has the value " << o2.m_i << std::endl <<
            "and now optr has the value " << (uintptr_t)optr << std::endl <<
            "and optr->m_i is now " << optr->m_i <<
            std::endl;
    
        if (optr != &o2)
            delete optr; // otherwise we'd technically be leaking memory.
    
        return 0;
    }
    

    Live demoideone.com。

    按值传递

    这个术语在 C++ 开发的早期使人们感到困惑,因为从通俗的角度来说,这听起来像是“Object& foo”会做的事情。

    术语“按值传递”实际上源于语言必须执行哪些操作才能调用此类函数,以按值方式将整个原始对象/结构复制到堆栈上,或者在复制 ctor 的情况下可用,将它们转发给一个值构造函数并重新创建原始值的副本,逐个值。

    值传递应该用于最简单的情况,在这些情况下,您不希望正在调用的函数对当前范围内的值产生副作用。

    bool checkWidthdrawl(Dollars balance, Dollars amountToWithdraw)
    {
        // it's safe for me to change "balance" here because balance is mine
    }
    

    bool checkWidthdrawl(Dollars& balance, Dollars amountToWithdraw)
    {
        balance -= amountToWithdraw;
        if (balance < 0)
            std::complaint << "My account seems to be missing $" << amountToWithdraw;
    }
    

    但是,通过引用传递可能会变得很昂贵。

    struct FourK { char a[1024], b[1024], c[1024], d[1024]; }
    

    如果你整天按值传递它,你可能会在某个时候炸毁你的堆栈,以及花费大量时间复制所有这些字节。

    void foo(int i); // Unless you need to see the changes to i, this is perfectly fine.
    void foo(FourK f); // Someone should hunt you down and yell "PEANUT" in your ear.
    

    通过引用传递

    引用实际上是指针系统的契约,它允许语言确保您真正谈论的是对象的具体实例,从而允许您引用函数外部值的预先存在的实例.

    当然,有办法打破这一点,但语言非常非常努力地试图让它们难以做到。例如,尝试将其添加到上面的代码中:

    Object& makeObjectNotWar(int i)
    {
        Object thisObjectGoesAway(i);
        return thisObjectGoesAway /*right about now*/;
    }
    

    您还可以向调用者保证该函数不会对带有“const”修饰符的变量产生任何副作用。

    void fooc(const Object& o)
    {
        o.m_i += 42; // Error
    }
    

    您甚至可以在函数中使用它来提示您自己(和编译器)不要意外更改值,在这种情况下,它可以为编译器提供优化提示:

    std::vector<int> foo;
    add1000valuesTo(foo);
    const size_t fooSize = foo.size();
    for (size_t i = 0; i < fooSize; ++i) {
        // ... stuff you're sure won't decrease foo.size()
    }
    

    没有 const fooSize

    for (size_t i = 0; i < foo.size(); ++i) {
    

    编译器必须首先假设“foo.size()”可以在任何给定的循环迭代中更改。它可能会发现它没有,但是通过给它提示,你节省了一点编译时间,可能提高了你的性能,并且让人们更容易准确地说出你期望的行为。缺点:如果你的循环确实改变了 foo 的大小,你会通过错误报告发现:(

    关于传递引用的最后一件事是 C++ 引用不受保护或“引用计数”。该语言只承诺引用在其范围内有效,只要您不做任何愚蠢的事情,比如调用删除对象的东西。

    // Author intended this function to be called
    // by the owner of a Dog.
    void doneWithFoo(Dog& dog)
    {
        Dog* deadDog = &dog;
        delete deadDog;
    }
    
    Rover& Babysitter::babysitDog(Dog& rover, int hours)
    {
        rover.feed(FeedType::Donut);
        if (rover.pooped())
           doneWithDog(rover);
        // ...
        return rover; // I have a bad feeling about this.
    }
    

    显然,您不会期望“babysitDog”会导致狗被丢弃。但请记住,因为我们传入了一个引用,所以它也从调用者那里消失了,它也从调用者那里消失了,如果那是使用引用... rover 死了,Dave,死了。

    与指针一样,如果您要存储超出您可以访问它们的范围的引用,那么您有责任确保被引用的对象仍然存在,或者在引用之前从容器中删除引用物体确实会消失。

    【讨论】:

    • 哇,我想运行它,但我得到了一些未定义的符号。我在 OS X 上使用 GCC-4.8。$ gcc-4.8 -W -Wall -Wextra -pedantic -std=c++0x -o object object.cpp 并为 x86_64/ 获取了一些未定义的符号:` std::basic_ostream >::operator >& (*)(std::basic_ostream >&)), 引用自`
    • Erm - 这是 C++ 代码,你想使用 g++-4.8 而不是 gcc-4.8。它在 Mint 15 下编译对我来说没有问题:osmith@olivia64 ~ $ g++-4.8 -O0 -g3 -Wall -Wextra -pedantic -std=c++11 -o object object.cpp(注意:-W = -Wall 所以你不需要两者)
    • 在答案末尾添加了一个额外的部分。
    猜你喜欢
    • 2014-04-24
    • 1970-01-01
    • 2011-08-28
    • 1970-01-01
    • 2013-08-11
    • 2011-07-06
    相关资源
    最近更新 更多