Какая разница между возвратом ссылки на const-объект (getter) и только строки? - PullRequest
0 голосов
/ 29 октября 2018

Я просматривал учебные материалы для веб-сайта c ++ как приятное дополнение к курсу в колледже, который я прохожу в этом семестре (для начинающих). Узнав о копирующих конструкторах и деструкторах , я наткнулся на этот раздел кода:

// 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;
}

Теперь я понимаю часть деструктора, но получатель для строкового члена меня смутил. Зачем возвращать ссылку (псевдоним) на объект (в данном случае строку)?

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

В чем разница между этим и просто возвращением строки?

string content() const {
    return *ptr;
}

Является ли возврат псевдонима const более эффективным? Вы возвращаете только адрес строки или саму строку? Как насчет того, чтобы просто вернуть строку, возвращаете ли вы всю строку? Спасибо.

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

получатель для строкового члена смутил меня. Зачем возвращать ссылка (псевдоним) на объект (в данном случае строка)?

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

В чем разница между этим [вернуть ссылку], и просто вернуть строку?

string content() const { return *ptr;}

И вы можете спросить, есть ли разница между этим и возвращает только указатель

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

  • Я не нахожу преимущества одного над другим.

Что ж, возможно, рассмотрим сценарий, в котором строка содержит 26 миллионов символов, возможно, вы захотите избежать копирования.

Но есть еще одна проблема (или, может быть, 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"

  • 1 миллион алфавитов

  • с по-прежнему только 32 байта

Исходя из этого, вы можете сделать вывод, что a) экземпляр 'string' занимает 32 байта независимо от данных. б) потому что все данные находятся в другом месте; в) поэтому некоторые из 32 байтов в экземпляре std :: string являются указателями на то, где в динамической памяти находятся символы.


Хм.

Если экземпляр obj составляет всего 32 байта, то вы можете спросить, почему Example4 использует указатель для помещения этого МАЛЫХ объектов (экземпляра строки) в динамическую память ... используя 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 
0 голосов
/ 29 октября 2018

Возвращение строки нежелательно по двум причинам:

  • Это означает, что выполняется ненужная копия строки, что плохо сказывается на производительности
  • Это также означает, что кто-то может попытаться изменить возвращенную строку, думая, что он изменяет фактический член класса - ссылка const не допустит этого и вызовет ошибку компиляции.
...