【问题标题】:What's the difference between returning a const object reference (getter), and just the string?返回 const 对象引用(getter)和仅返回字符串有什么区别?
【发布时间】:2018-10-29 19:23:41
【问题描述】:

我正在浏览 c++ 网站教程,作为对本学期(初学者)上大学课程的一个很好的补充。在学习copy constructors and destructors的过程中,偶然发现了这段代码:

// destructors
#include <iostream>
#include <string>
using namespace std;

class Example4 {
    string* ptr;
  public:
    // constructors:
    Example4() : ptr(new string) {}
    Example4 (const string& str) : ptr(new string(str)) {}
    // destructor:
    ~Example4 () {delete ptr;}
    // access content:
    const string& content() const {return *ptr;}
};

int main () {
  Example4 foo;
  Example4 bar ("Example");

  cout << "bar's content: " << bar.content() << '\n';
  return 0;
}

现在,我了解了析构函数部分,但字符串成员的 getter 让我感到困惑。为什么要返回对对象(本例中为字符串)的引用(别名)?

// access content:
const string& content() const {return *ptr;}

这和只返回字符串有什么区别?

string content() const {
    return *ptr;
}

返回 const 别名是否更有效?您是只返回字符串的地址,还是返回字符串本身?仅返回字符串时,您是否返回整个字符串呢?谢谢。

【问题讨论】:

  • 如果您的课程告诉您newdelete 适合“使用对象”,那么它们是在欺骗您或试图让您编写Java。那应该是class Example4 { string str; public: Example4() {} Example4 (const string&amp; str) : str(str) {} const string&amp; content() const {return str;} };(如果有的话,它没有任何行为)。最好的构造函数是隐式的。
  • 在堆上分配一个std::string(使用new)是糟糕的编码实践,因为std::string已经在管理它的内存(如果很长,就在堆上) .

标签: c++ c++11 constants return-value return-type


【解决方案1】:

返回一个字符串是不可取的,原因有两个:

  • 这意味着执行了不必要的字符串副本,这对性能不利
  • 这也意味着有人可能会尝试修改返回的字符串,认为他们修改了类的实际成员 - const 引用不允许这样做,并触发编译错误。

【讨论】:

  • 另一方面,返回一个引用意味着调用者可以在没有警告的情况下形成一个悬空引用。
【解决方案2】:

字符串成员的吸气剂让我很困惑。为什么返回一个 对对象(本例中为字符串)的引用(别名)?

const string& content() const {return *ptr;}

[return a reference] 有什么区别, 只是返回字符串?

string content() const { return *ptr;}

你可能会问这两者之间是否有区别 并且只返回指针

const string* content() const { return ptr;} 

  • 我发现其中一个没有优势。

好吧,也许考虑一下字符串包含 2600 万个字符的场景,您可能希望避免复制它。

但如果只是为了评估您在这里学到的知识,您应该注意另一个问题(或者可能是 2 个)。


在 Lubuntu 18.04 上,使用 g++ (Ubuntu 7.3.0-27),字符串 s,没有数据,

std::string s; 
cout << sizeof(s) << "  " << s.size() << endl;

报告数字“32 0”。


std::string s ("01234567890123456789"); 
cout << sizeof(s) << "  " << s.size() << endl;

报告值“32 20”


{
   std::string s;
   for (int i=0; i<1000000; i++)
   {
      for (char j='A'; j<='Z'; j++)
         s.push_back(j);
   }
   cout << "  " << sizeof(s) << "  " << s.size() << endl;
}

这会报告值“32 26000000”

  • 100 万个字母

  • s 仍然只有 32 个字节

由此,您可以得出以下结论:a) 'string' 的实例与数据无关,占用 32 个字节。 b) 因为所有数据都驻留在其他地方 c) 所以 std::string 实例中的 32 个字节中的一些是指向动态内存中字符所在位置的指针。


嗯。

如果 obj 实例只有 32 个字节,那么你可能会问为什么 Example4 使用指针将这个 SMALL 对象(字符串实例)放入动态内存中……使用 8 个字节找到 32,然后需要一个第二个引用(字符串实例内部的某个指针)到达 Example4 字符串的字符。

同样,一个 std::vector 是 24 字节(不管有多少元素,也不管元素有多大)。 std::vector 负责内存管理,因此您不必这样做。

也许本课旨在帮助您发现和评估动态记忆中的内容以及自动记忆中的内容,以改进您的选择。

关键思想是 STL 库容器为您处理动态内存,从而大大简化您的工作。


或者,也许教授希望您更多地了解您正在使用的工具。在某些方面,标准容器将您与这些东西的工作方式隔离开来。也许这个任务是为了了解 std::string 的作用。


//这里是一些“g++ -std=c++17”代码,单步执行,说明了几个想法

#include <iostream>
using std::cout, std::endl;

#include <sstream>
using std::stringstream;

#include <iomanip>
using std::setfill, std::setw;

#include <string>
using std::string;

#include <cstring>
using std::strlen;


class Example4
{
   string* ptr;
public:
   Example4() : ptr(new string) {}
   Example4 (const string& str) : ptr(new string(str)) {}
   ~Example4 () {delete ptr;}

   // access content:
   const string& content() const {return *ptr;}
   const string* contentP() const {return  ptr;}

   string show(string lbl)
      {
         stringstream ss;
         ss << "\n  " << lbl
            <<             "               .  5         4         3         2         1"
            << "\n                         . '09876543210987654321098765432109876543210987654321'"
            << "\n  " << "*ptr                   : '" << *ptr  << "'"
            << "\n  " << "(*ptr).size()          : " << (*ptr).size()
            << "\n  " << "  ptr->size()          : " <<   ptr->size()
            << "\n  " << "strlen((*ptr).c_str()) : " << strlen((*ptr).c_str())
            << "\n  " << "strlen(ptr->c_str())   : " << strlen(ptr->c_str())
            << "\n\n  " << "sizeof(*ptr)           : " << sizeof(*ptr)

            << "     @ 0x" << ptr << ',' // where ptr points to
            << "\n  " << "sizeof (ptr)           : " << sizeof(ptr)
            << "\n\n";
         return ss.str();
      }
};



class T996_t
{
public:
   int operator()() { return exec(); }

private: // methods

   int exec()
      {
         Example4 e4("Now is the time to answer all questions01234567890");

         cout << "\n  "  <<  e4.show("Example4")
              << "\n  '" <<  e4.content() << "'"
              << "\n  '" << *e4.contentP() << "'\n\n"
              << endl;

         {
            std::string s;
            cout << "  " << sizeof(s) << "  " << s.size() << endl;
         }

         {
            std::string s("01234567890123456789");
            cout << "  " << sizeof(s) << "  " << s.size() << endl;
         }

         {
            std::string s;
            for (int i=0; i<1000000; i++)
            {
               for (char j='A'; j<='Z'; j++)
                  s.push_back(j);
            }
            cout << "  " << sizeof(s) << "  " << s.size() << endl;
         }

         return 0;
      }

}; // class T996_t

int main(int, char**) { return T996_t()(); }

这段代码在我的 Lubuntu 上编译和运行。我的make文件构建的编译命令以:

开头
g++ -std=c++17 -m64 -ggdb 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-07
    • 1970-01-01
    相关资源
    最近更新 更多