Почему C ++ не поддерживает функции, возвращающие массивы? - PullRequest
44 голосов
/ 01 марта 2011

Некоторые языки позволяют вам просто объявить функцию, возвращающую массив, как обычную функцию, например Java:

public String[] funcarray() {
   String[] test = new String[]{"hi", "hello"};
   return test;
}

Почему C ++ не поддерживает что-то вроде int[] funcarray(){}? Вы можете вернуть массив, но создать такую ​​функцию - настоящая проблема. А еще я где-то слышал, что строки - это просто массивы char. Так что, если вы можете вернуть строку в C ++, почему бы не массив?

Ответы [ 9 ]

60 голосов
/ 01 марта 2011

Держу пари, что если быть кратким, то это было просто дизайнерское решение.Более конкретно, если вы действительно хотите знать почему, вам нужно работать с нуля.

Давайте сначала подумаем о C.В языке C существует четкое различие между «передачей по ссылке» и «передачей по значению».Проще говоря, имя массива в C на самом деле просто указатель.Для всех намерений и целей разница (как правило) сводится к распределению.Код

int array[n];

создаст 4 * n байтов памяти (в 32-битной системе) в стеке, что соответствует объему любого блока кода, который делает объявление.В свою очередь,

int* array = (int*) malloc(sizeof(int)*n);

создаст такой же объем памяти, но в куче.В этом случае то, что находится в этой памяти, не связано с областью, только ссылка на память ограничена областью.Здесь происходит передача по значению и передача по ссылке. Как вы, вероятно, знаете, передача по значению означает, что когда что-то передается или возвращается из функции, то «вещь», которая передается, является результатом вычисления переменной.Другими словами,

int n = 4;
printf("%d", n);

напечатает число 4, потому что конструкция n оценивается как 4 (извините, если это элементарно, я просто хочу охватить все основания).Эта четверка не имеет абсолютно никакого отношения к пространству памяти вашей программы и не имеет отношения к ней, это всего лишь литерал, и поэтому, как только вы покинете область, в которой эта четверка имеет контекст, вы потеряете ее.Как насчет передачи по ссылке?Передача по ссылке ничем не отличается в контексте функции;Вы просто оцениваете конструкцию, которая передается.Единственное отличие состоит в том, что после оценки переданной «вещи» вы используете результат оценки в качестве адреса памяти.У меня когда-то был один циничный инструктор по CS, который любил утверждать, что нет такой вещи, как передача по ссылке, просто способ передать умные значения.На самом деле он прав.Итак, теперь мы думаем о сфере с точки зрения функции.Представьте, что у вас может быть тип возвращаемого массива:

int[] foo(args){
    result[n];
    // Some code
    return result;
}

Проблема здесь в том, что результат оценивается по адресу 0-го элемента массива.Но когда вы пытаетесь получить доступ к этой памяти извне этой функции (через возвращаемое значение), у вас возникает проблема, потому что вы пытаетесь получить доступ к памяти, которая находится вне области действия, с которой вы работаете (стек вызова функции).Таким образом, мы можем обойти это с помощью стандартной jiggery-pokery «передачи по ссылке»:

int* foo(args){
    int* result = (int*) malloc(sizeof(int)*n));
    // Some code
    return result;
}

Мы все еще получаем адрес памяти, указывающий на 0-й элемент массива, но теперь у нас есть доступ кэто воспоминание.

Что я имею в виду?В Java принято утверждать, что «все передается по значению».Это правда.Тот же самый циничный инструктор из вышеупомянутого также говорил о Java и ООП в целом: все является лишь указателем.И он тоже прав.Хотя все в Java фактически передается по значению, почти все эти значения на самом деле являются адресами памяти.Таким образом, в Java язык позволяет вам возвращать массив или строку, но он делает это, превращая его в версию с указателями для вас.Он также управляет вашей памятью для вас.А автоматическое управление памятью, хотя и полезно, но неэффективно.

Это приводит нас к C ++. Единственная причина, по которой был изобретен С ++, заключалась в том, что Бьярн Страуструп экспериментировал с Симулой (в основном оригинальным OOPL) во время своей докторской работы и думал, что это было фантастически концептуально, но он заметил, что он работал довольно ужасно. И поэтому он начал работать над тем, что называлось C с классами, которое было переименовано в C ++. При этом его целью было создать язык программирования, который бы использовал НЕКОТОРЫЕ из лучших функций Simula, но оставался мощным и быстрым. Он решил расширить C из-за его и без того легендарной производительности, и одним из компромиссов было то, что он решил не реализовывать автоматическое управление памятью или сборку мусора в таком большом масштабе, как другие OOPL. Возврат массива из одного из шаблонных классов работает, потому что, ну, вы используете класс. Но если вы хотите вернуть массив C, вы должны сделать это способом C. Другими словами, C ++ поддерживает возврат массива ТОЧНО так же, как Java; он просто не делает всю работу за вас. Потому что датский чувак думал, что это будет слишком медленно.

31 голосов
/ 01 марта 2011

C ++ поддерживает это - ну вроде:

vector< string> func()
{
   vector<string> res;
   res.push_back( "hello" );
   res.push_back( "world" );
   return res;
}

Даже C поддерживает это:

struct somearray
{
  struct somestruct d[50];
};

struct somearray func()
{
   struct somearray res;
   for( int i = 0; i < 50; ++i )
   {
      res.d[i] = whatever;
   }
   // fill them all in
   return res;
}

A std::string - это класс, но когда вы говоритеСтрока, вы, вероятно, имеете в виду буквальный.Вы можете безопасно возвращать литерал из функции, но на самом деле вы можете статически создать любой массив и вернуть его из функции.Это было бы поточно-ориентированным, если бы это был константный (только для чтения) массив, который имеет место со строковыми литералами.

Массив, который вы возвращаете, будет ухудшаться до указателя, поэтому вы не сможете работатьего размер только от его возвращения.

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

Помните, что в ранние времена C все переменные должны были быть объявлены в верхней части функции, и вы не могли просто объявить при первом использовании.Таким образом, в то время это было неосуществимо.

Они дали обходной путь помещения массива в структуру, и именно так он теперь и должен оставаться в C ++, поскольку он использует то же соглашение о вызовах.

Примечание. В таких языках, как Java, массив - это класс.Вы создаете один с новым.Вы можете переназначить их (они являются l-значениями).

26 голосов
/ 01 марта 2011

Массивы в C (и в C ++ для обратной совместимости) имеют особую семантику, которая отличается от остальных типов.В частности, в то время как для остальных типов C имеет только семантику передачи по значению, в случае массивов эффект синтаксиса передачи по значению имитирует передачу по ссылке странным образом:

В сигнатуре функции аргумент типа массив из N элементов типа T преобразуется в указатель на T .При вызове функции передача массива в качестве аргумента функции приведет к распаду массива на указатель на первый элемент , и этот указатель будет скопирован в функцию.

Из-за этой особой обработки массивов - они не могут быть переданы по значению - они также не могут быть возвращены по значению.В C вы можете вернуть указатель, а в C ++ вы также можете вернуть ссылку, но сам массив не может быть размещен в стеке.

Если вы думаете об этом, это не отличается от языка, который выиспользуются в вопросе, так как массив динамически размещается, и вы только возвращаете указатель / ссылку на него.

Язык C ++, с другой стороны, допускает различные решения этой конкретной проблемы, например использование std::vector в текущем стандарте (содержимое динамически распределяется) или std::array в следующем стандарте (содержимое может быть выделено в стеке, но это может иметь большую стоимость, поскольку каждый элемент должен быть скопирован в тех случаях, когдакопия не может быть исключена компилятором).Фактически, вы можете использовать подход такого же типа с текущим стандартом, используя готовые библиотеки, такие как boost::array.

8 голосов
/ 01 марта 2011

"Вы не можете вернуть массив из функции, потому что этот массив будет объявлен внутри функции, и его расположение будет тогда фреймом стека. Однако, фрейм стека стирается при выходе из функции. Функции должны копировать returnзначение из фрейма стека в местоположение возврата, что невозможно с массивами. "

Из обсуждения здесь:

http://forum.codecall.net/c-c/32457-function-return-array-c.html

7 голосов
/ 01 марта 2011

Другие говорили, что в C ++ один использует вектор <> вместо массивов, унаследованных от C.

Так почему C ++ не позволяет возвращать массивы C?Потому что С нет.

Почему С нет?Поскольку C развился из B, нетипизированный язык, в котором возвращение массива вообще не имеет смысла.При добавлении типов в B было бы целесообразно сделать возможным возвращение массива, но это не было сделано для того, чтобы поддерживать правильность некоторых идиом B и упростить преобразование программ из B в C. И с тех пор появилась возможностьсделать массивы C более удобными для использования, как всегда было отказано (и даже больше, даже не рассмотрено), поскольку это сломало бы слишком много существующего кода.

3 голосов
/ 01 марта 2011

Вы можете вернуть указатель на массив.Просто будьте осторожны с освобождением памяти позже.

public std::string* funcarray() {
    std::string* test = new std::string[2];
    test[0] = "hi";
    test[1] = "hello";
    return test;
}

// somewhere else:
std::string* arr = funcarray();
std::cout << arr[0] << " MisterSir" << std::endl;
delete[] arr;

Или вы можете просто использовать один из контейнеров в пространстве имен std, например std :: vector.

2 голосов
/ 01 марта 2011

«Почему C ++ не поддерживает что-то вроде»: потому что это не имеет никакого смысла. В ссылочных языках, таких как JAVA или PHP, управление памятью основано на сборке мусора. Части памяти, которые не имеют ссылок (ни одна переменная в вашей программе больше не указывает на это) автоматически освобождаются. В этом контексте вы можете выделить память и бездумно передать ссылку.

Код C ++ будет переведен в машинный код, и в нем не определен GC. Таким образом, в C и C ++ ощущается владение блоками памяти. Вы должны знать, свободен ли указатель, которым вы пользуетесь, в любое время (на самом деле вы shoud освобождаете его после использования), или у вас есть указатель на разделяемую часть памяти, что является абсолютным нет -нет бесплатно.

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

Будет ли массив, возвращаемый функцией, всегда копией (ваша бесплатно) или вам придется делать их копии? Как бы вы выиграли, получив массив из указателя на массив?

1 голос
1 голос
/ 01 марта 2011

Возвращает std::vector<> вместо массива.В целом, массивы не работают хорошо с C ++, и их, как правило, следует избегать.

Кроме того, тип данных string - это не просто массив символов, хотя "строка в кавычках" есть.string управляет массивом символов, и вы можете получить к нему доступ с помощью .c_str(), но есть еще string.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...