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

c++ prevent changing reference through getter

本文关键字:引用 getter C++      更新时间:2023-10-16

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

我有一个类B,其中包含类A的成员,我希望类B的用户能够更改类A中的值,但不能更改类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"
    }
}

你写道:

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的副本。换句话说,你已经把名字改成了"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还有其他属性你不想被更改,并且赋值会改变它们,那么不是通过B::getA()公开整个A对象,只需添加一个新的成员函数来设置名称B。这样做比简单地公开整个A对象更好。Java 和 C# 似乎经常鼓励糟糕的设计,涉及所有内容的 getter 和 setter,这是毫无意义的,什么都没有封装;您不妨将每个成员都公开,如果所有成员都有设置器,则直接访问它们。

如果你想要一个包含除名称外不会更改的AB,则不要公开整个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#代码中,你实际上永远不会允许/使用非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的访问,而只返回一个副本。通常(如果A是非平凡的和/或大的),这比仅仅返回引用(如指针)需要更多的努力,但就用法/效果而言,它与getA() const非常相似(如果某些const A成员实际上改变了A的状态,或者如果您使用可怕的const_cast<>,则有所不同const_cast<A&>(b.getA())=otherA)。

您可能希望使用 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 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"
}

显式禁用值复制!

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