【问题标题】:c++ prevent changing reference through getterc ++防止通过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; 并没有像你想象的那样做。

标签: c++ class reference getter


【解决方案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&);
      

      【讨论】:

      • 如何允许赋值运算符被调用并表现出异常行为,帮助?
      猜你喜欢
      • 1970-01-01
      • 2020-04-25
      • 1970-01-01
      • 2017-11-26
      • 1970-01-01
      • 2011-08-18
      • 2014-02-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多