Почему C++ поддерживает членное назначение массивов внутри структур, но не в целом?


Я понимаю, что memberwise назначение массивов не поддерживается, так что следующее не будет работать:

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

Я просто принял это как факт, полагая, что цель языка-обеспечить открытую структуру, и пусть пользователь решает, как реализовать что-то вроде копирования массива.

однако следующие работы:

struct myStruct {int num[3];};
myStruct struct1={{1,2,3}};
myStruct struct2;
struct2 = struct1;

массив num[3] присваивается члену из его экземпляра в struct1, в его экземпляр в struct2.

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

edit:Роджер Паштеткомментарий в потоке std:: string в struct-Copy / assignment issues? кажется, указывает в общем направлении ответа, но я не знаю достаточно, чтобы подтвердить это сам.

edit 2: много прекрасных ответов. Я выбираю Лютер Blissett ' s потому что я в основном задавался вопросом о философском или историческом обосновании поведения, но Джеймс McNellisссылка на соответствующую спецификационную документацию также была полезной.

4 75

4 ответа:

вот мой взгляд на это:

развитие языка C предлагает некоторое представление об эволюции типа массива в C:

я постараюсь обрисовать массив вещь:

Си Б и не нуждающийся в представлении не имели никакого внятного тип массива, объявления, как:
auto V[10] (B)
or 
let V = vec 10 (BCPL)

объявит V как (нетипизированный) указатель, который инициализируется для указания на неиспользуемую область из 10 "слов" памяти. Б уже используется * для разыменования указателя и [] короткая нотация руки,*(V+i) означает V[i], как и в C/C++ сегодня. Однако,V это не массив, это все еще указатель, который должен указывать на некоторую память. Это вызвало проблемы, когда Деннис Ричи попытался расширить B с помощью типов структур. Он хотел, чтобы массивы были частью структур, как в C сегодня:

struct {
    int inumber;
    char name[14];
};

но с Б,понятия не нуждающийся в представлении массивов в качестве указателей, это потребовало бы name поле, содержащее указатель, который должен быть инициализируется во время выполнения в область памяти размером 14 байт внутри структуры. Проблема инициализации / компоновки была в конечном итоге решена путем предоставления массивам специального режима: компилятор будет отслеживать местоположение массивов в структурах, в стеке и т. д. фактически не требуя, чтобы указатель на данные материализовался, за исключением выражений, которые включают массивы. Этот лечение позволило почти весь код B по-прежнему работать и является источником "массивы преобразуются в указатель, если вы посмотрите на них" правило. Это совместимость Hack, который оказался очень кстати, потому что это позволило массивами открытых размер и т. д.

и вот мое предположение, почему массив не может быть назначен: поскольку массивы были указателями в B, вы могли бы просто написать:

auto V[10];
V=V+5;

для перебазирования "массива". Сейчас это было бессмысленно, потому что база переменной массива не больше значения. Таким образом, это назначение было запрещено, что помогло поймать несколько программ, которые сделали эту перебазировку об объявленных массивов. И тогда это понятие застряло: поскольку массивы никогда не были предназначены для первого класса системы типа C, они в основном рассматривались как специальные звери, которые становятся указателями, если вы их используете. И с определенной точки зрения (которая игнорирует, что c-массивы являются неудачным взломом), запрещение назначения массива все еще имеет некоторый смысл: открытый массив или параметр функции массива обрабатывается как указатель без информации о размере. Компилятор не имеет информации для создания назначения массива для них, и назначение указателя было необходимо по соображениям совместимости. Введение назначения массива для объявленных массивов привело бы к ошибкам, хотя ложные назначения (является ли a=b назначением указателя или элементарной копией?) и другие проблемы (как вы передаете массив по значению?) без фактического решения проблемы-просто сделайте все явно с memcpy!

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It's actually better to leave things as they are...
*/
typedef int vec[3];

void f(vec a, vec b) 
{
    vec x,y; 
    a=b; // pointer assignment
    x=y; // NEW! element-wise assignment
    a=x; // pointer assignment
    x=a; // NEW! element-wise assignment
}

это не изменилось, когда пересмотр C в 1978 году добавил назначение структуры (http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf). Даже если записи были различные типы В C, было невозможно назначить их в начале K&R C. Вы должны были скопировать их по-членски с помощью memcpy, и вы могли передавать только указатели на них в качестве параметров функции. Назначение (и передача параметров) теперь было просто определено как memcpy необработанная память структуры, и поскольку это не могло сломать существующий код, он был легко adpoted. В качестве непреднамеренного побочного эффекта это неявно вводило какое-то назначение массива, но это происходило где-то внутри структуры, поэтому это не могло действительно ввести проблемы с использованием массивов.

Что касается операторов присваивания, стандарт C++ говорит следующее (C++03 §5.17/1):

Существует несколько операторов присваивания... все требуют модифицируемого значения lvalue в качестве их левого операнда

массив-это не изменяемое значение.

однако назначение объекту типа класса определяется специально (§5.17 / 4):

назначение объектов класса определяется назначением копии оператор.

Итак, мы посмотрим, что делает неявно объявленный оператор присваивания копии для класса (§12.8 / 13):

неявно определенный оператор присваивания копии для класса X выполняет членное присваивание его подобъектов. ... Каждый подобъект присваивается способом, соответствующим его типу:
...
-- если подобъект является массивом, каждому элементу присваивается соответствующий тип элемента
...

Так, для объекта типа класса, массивы копируются правильно. Обратите внимание, что если вы предоставляете объявленный пользователем оператор назначения копирования, вы не можете воспользоваться этим, и вам придется копировать массив элемент за элементом.


рассуждение аналогично в C (C99 §6.5.16 / 2):

оператор присваивания должен иметь изменяемое значение lvalue в качестве левого операнда.

и §6.3.2.1/1:

A модифицируемое именующее является lvalue, который не имеет тип массива... [другие ограничения следуют]

в C назначение намного проще, чем в C++ (§6.5.16.1/2):

в простом присваивании ( = ) значение правого операнда преобразуется к типу выражение присваивания и заменяет значение, хранящееся в объекте, обозначенном слева операнд.

для назначения объектов типа struct левый и правый операнды должны иметь тот же тип, поэтому значение правого операнда просто копируется в левый операнд.

по этой ссылке:http://www2.research.att.com / ~bs/bs_faq2.html есть раздел о присвоении массива:

две фундаментальные проблемы с массивами, что

  • массив не знает свой собственный размер
  • имя массива преобразуется в указатель на его первый элемент по малейшему поводу

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

Итак, компилятор не может сказать разницу между int a[10] и int b[20].

структуры, однако, не имеют такой же двусмысленности.

Я знаю, все, кто ответил, являются экспертами в C / C++. Но я подумал, что это основная причина.

num2 = num1;

здесь вы пытаетесь изменить базовый адрес массива, что не допустимо.

и конечно, struct2 = struct1;

здесь объект struct1 присваивается другому объекту.