C++ 防止通过 getter 更改引用

c++ prevent changing reference through getter

我目前正在学习 C++,我是一名经验丰富的 C# 和 Java 开发人员。

我有一个 class B,其中包含 class A 的成员,我希望 class B 的用户能够更改 class A 中的值,但不能更改 class A 本身的实例。

基本上它想阻止b.getA() = anotherA;被允许。

这在 C++ 中可行吗?还是我的设计在这里完全错误?

这是我的小c++程序

#include <string>
#include <iostream>

using namespace std;

class A {
public:
    string getName()
    {
        return name;
    }
    void setName(string value)
    {
        name = value;
    }
private:
    string name = "default";
};

class B {
public:
    A &getA()
    {
        return anInstance;
    }
private:
    A anInstance;
};

int main(int argc, char** argv) {
    B b;
    cout << b.getA().getName() << std::endl; // outputs "default"

    b.getA().setName("not default");
    cout << b.getA().getName() << std::endl; // outputs "not default"

    A a;
    a.setName("another a instance");
    b.getA() = a; // I want to prevent this being possible 
    cout << b.getA().getName() << std::endl; // outputs "another a instance"
}

我正在尝试做的 C# 示例

class Program
{
    class A
    {
        private string name = "default";
        public string getName()
        {
            return name;
        }
        public void setName(string value)
        {
            name = value;
        }
    }

    class B
    {
        private A anInstance;
        public A getA()
        {
            return anInstance;
        }
    }

    static void Main(string[] args)
    {
        B b = new B();
        Console.WriteLine(b.getA().getName()); // outputs "default"

        b.getA().setName("not default");
        Console.WriteLine(b.getA().getName()); // outputs "not default"
    }
}

明确禁用值复制!

private:
    A(const A&);
    A& operator=(const A&);

您可能想使用 const 指针而不是引用,但它可能有很多副作用:

class A {
public:
string getName() const
{
    return name;
}
void setName(string value)
{
    name = value;
}
private:
    string name;
};

class B {
public:
A * const getA() const
{
    return anInstance;
}
 private:
     A* anInstance;
};

int main(int argc, char** argv) {
    B b;
    cout << b.getA()->getName() << std::endl; // outputs "default"

    b.getA()->setName("not default");
    cout << b.getA()->getName() << std::endl; // outputs "not default"

    A a;
    a.setName("another a instance");
    b.getA() = a; // I want to prevent this being possible 
    cout << b.getA()->getName() << std::endl; // outputs "another a instance"
}

正如@Captain Price 提到的,您可以禁止 A class:

的 =operator
class A {
public:
string getName()
{
    return name;
}
void setName(string value)
{
    name = value;
}
private:
string name;
A& operator=(const A& other);
}; 

class B {
public:
A &getA()
{
    return anInstance;
}
private:
A anInstance;
};

int main(int argc, char** argv) {
B b;
cout << b.getA().getName() << std::endl; // outputs "default"

b.getA().setName("not default");
cout << b.getA().getName() << std::endl; // outputs "not default"

A a;
a.setName("another a instance");
b.getA() = a; // I want to prevent this being possible 
cout << b.getA().getName() << std::endl; // outputs "another a instance"
}

您写道:

A a;
a.setName("another a instance");
b.getA() = a; // I want to prevent this being possible 

请问,为什么?

为什么要阻止这种情况?

你的下一行是:

cout << b.getA().getName() << std::endl; // outputs "another a instance"

但这是误导,你没有改变 b 里面的 A 的实例,你只改变了 b.anInstance 到成为 a 副本 。换句话说,您已将名称更改为 say "another a instance" 但这并不意味着它是真的。与您调用 b.getA().setName("another a instance") 相比,它不是另一个实例(事实上,结果与这样做相同!)

试一试:

A a;
a.setName("another a instance");
std::cout << &b.getA() << std::endl;
b.getA() = a;
std::cout << &b.getA() << std::endl;

你会得到两次打印的相同地址,因为 b.getA() = a 不会 替换 b.anInstance,它只是修改它,就像调用 setName 确实如此。

那是因为在 C++ 中 B::anInstance 不仅仅是对 A 的引用,它 一个 A,因此通过赋值对它你不改变引用指向不同的 A,你改变 A 本身。

因此,回到您最初的问题,既然您担心的事情 无论如何都不会发生 为什么您需要阻止它?如果您已经允许通过 setName() 函数修改 b.anInstance,为什么不让它也通过赋值修改?

如果该问题的答案是 A 还有其他您不想更改的属性,并且赋值会更改它们,那么与其公开整个 A object via B::getA() 只需添加一个新的成员函数到 B 来设置名称。这样做比简单地暴露整个 A 对象更好的封装。 Java 和 C# 似乎常常鼓励涉及 getter 和 setter 的糟糕设计,这是毫无意义的,也没有封装任何东西;如果所有内容都有 setter,您不妨让每个成员 public 并直接访问它们。

如果你想要一个 B 包含一个 A 除了它的名字不会改变,那么不要公开整个 A,只需提供一个 setter 在外部对象上:

class A {
public:
    string getName() const // N.B. Added 'const' here
    {
        return name;
    }
    void setName(string value)
    {
        name = value;
    }
private:
    string name = "default";
};

class B {
public:
    // Read-only access to the contained object:
    const A& getA() const
    {
        return anInstance;
    }

    // Update the name of the contained object:
    void setName(string value)
    {
        anInstance.name = value;
    }

private:
    A anInstance;
};

当您允许一般非 const 访问成员(例如 A B::anInstance)时,这意味着可以访问该成员的所有(public)成员(const 而不是)。特别是,您提供对 operator= 的访问权限,这允许更改成员的 内容 。当然还是原来的A(地址不变),只是数据变了。

在您的 C# 代码中,您实际上永远不会 allowing/using 非 const 访问 B::anInstance,因此您的 C++ 并不是真正等价的。考虑

 class B
 {
   A anInstance; // private by default
 public:
   A const& getA() const { return anInstance; } // const access only
   A      & getA()       { return anInstance; } // full access
   A       copyA() const { return anInstance; } // make a copy 
 };

第一个 getA() 可以从 const B 访问并且只允许 const 访问 anInstance,即只允许 Aconst 成员访问].第二个 getA() 只能从非 const B 访问,并允许完全(public)访问 anInstance,包括 A::operator=(A const&)(如果存在) ,即在 class A).

的定义中声明或不声明 =delete

最后,copyA() 不提供对 B::anInstance 的任何访问权限,但 returns 只是对其的一个副本。通常(如果 A 是非平凡的 and/or 大)这比仅仅返回一个引用(如指针)需要更多的努力,但就 usage/effect 而言,它非常类似于getA() const(如果 A 的某些 const 成员实际上改变了 A 的状态,或者如果你使用可怕的 const_cast<> 就像 const_cast<A&>(b.getA())=otherA ).