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

Posted

技术标签:

【中文标题】c ++防止通过getter更改引用【英文标题】:c++ prevent changing reference through getter 【发布时间】:2015-03-13 15:16:44 【问题描述】:

我目前正在学习 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"
    

【问题讨论】:

你知道const吗? string getName() 应该是 const string&amp; getName() const (这只有在返回成员变量时才可以)和 setName(string value) 应该是 setName(const string&amp; value) 这会减少字符串的副本。 他在问题中特别说“我希望B类的用户能够修改A类”。这不是关于 const,而是关于他的假设,即分配给调用者的引用也会改变 B 中的引用,但它不会。不要那么粗鲁和傲慢。 允许调用者修改 A 但不允许从另一个实例分配 A 是相当矛盾的,因为后者是前者的特例。您可以在 A 类上禁用 operator= @JfBeaulac 简而言之,b.getA() = anotherA; 并没有像你想象的那样做。 【参考方案1】:

你写道:

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 的其他属性,并且分配会更改它们,那么不要通过 B::getA() 公开整个 A 对象只需向 B 添加一个新的成员函数即可设置名称。这样做比简单地暴露整个A 对象更好的封装。 Java 和 C# 似乎经常鼓励糟糕的设计,包括所有东西的 getter 和 setter,这是没有意义的,也没有封装任何东西;如果所有内容都有设置器,您不妨将每个成员公开并直接访问它们。

如果你想要一个 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;
;

【讨论】:

【参考方案2】:

当您允许一般非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,即只允许constA 成员访问。第二个getA() 只能从非const B 访问,并允许完全(public)访问anInstance,包括A::operator=(A const&amp;)(如果存在,即在定义中声明或未声明=delete class A)。

最后,copyA() 不提供对B::anInstance 的任何访问权限,而只返回一个副本。通常(如果A 不重要和/或很大)这比仅仅返回一个引用(如指针)需要更多的努力,但在使用/效果方面,它与getA() const 非常相似(不同如果A 的某些const 成员实际上改变了A 的状态,或者如果您使用可怕的const_cast&lt;&gt;,如const_cast&lt;A&amp;&gt;(b.getA())=otherA)。

【讨论】:

【参考方案3】:

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

【讨论】:

您的示例不起作用,因为 anInstance 从未初始化。 @NeilKirk 它不起作用,因为它没有编译。您可以根据需要初始化 A*。问题不在于这个。 如果您给出一个看起来可以编译和运行的示例,它应该可以成功运行,或者您应该明确声明它无法运行。 @NeilKirk PS 寻找一种方法使此代码无法编译。你在说什么? Captain Price 的回答并不建议禁止赋值运算符。【参考方案4】:

明确禁用值复制!

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

【讨论】:

如何允许赋值运算符被调用并表现出异常行为,帮助?

以上是关于c ++防止通过getter更改引用的主要内容,如果未能解决你的问题,请参考以下文章

C如何防止头文件中的结构体被重复定义?

通过单击事件处理程序防止 routerLink 更改路由

通过using声明改变个别成员的可访问性

持有C ++引用是否可以防止变量被破坏?

如果通过 Mongoose 在 Mongodb 的另一个文档中存在引用,有啥更好的方法可以防止文档被删除?

为什么代理属性设置成assign为了防止生成保留环来