Pull to refresh

Comments 67

Обычно при добавлении нового элемента в вектор и нехватке места его увеличивают не на 1 (как, насколько я понял из кода, случится у вас), а в какое-то количество раз, например, в полтора раза. Это уменьшает амортизированную сложность добавления до O(1) при заполнении вектора. (Там всплывают ещё тонкости на тему оптимального выбора, вроде того, что нельзя увеличивать в два и более раз, и так далее. Всё это хорошо бы учесть.)

Да, знаю про это. Как-никак один из важнейших нюансов при написании динамического массива. :)


Нет, у меня именно что происходит увеличение в 1.5 раза. Причём коэффициент можно изменить, переопределив GVEC_GROWTH_FACTOR при компиляции библиотеки.


Значение 1.5 выбрано из-за тех самых тонкостей с оптимальным выбором. Вот хороший ответ на SO, после прочтения которого мне стали понятны преимущества: ссылка.

О, извините. Недостаточно внимательно читал)
Но поднять эту тему в таком топике полезно в любом случае.
Еще очень прикольная оптимизация описанна в
доках к Qt.
We build the string out dynamically by appending one character to it at a time. Let's assume that we append 15000 characters to the QString string. Then the following 18 reallocations (out of a possible 15000) occur when QString runs out of space: 4, 8, 12, 16, 20, 52, 116, 244, 500, 1012, 2036, 4084, 6132, 8180, 10228, 12276, 14324, 16372. At the end, the QString has 16372 Unicode characters allocated, 15000 of which are occupied.

The values above may seem a bit strange, but here are the guiding principles:

QString allocates 4 characters at a time until it reaches size 20.
From 20 to 4084, it advances by doubling the size each time. More precisely, it advances to the next power of two, minus 12. (Some memory allocators perform worst when requested exact powers of two, because they use a few bytes per block for book-keeping.)
From 4084 on, it advances by blocks of 2048 characters (4096 bytes). This makes sense because modern operating systems don't copy the entire data when reallocating a buffer; the physical memory pages are simply reordered, and only the data on the first and last pages actually needs to be copied.
В Delphi 3 (1997ого года) сделано так:
procedure TList.Grow;
var
  Delta: Integer;
begin
  if FCapacity > 64 then Delta := FCapacity div 4 else
    if FCapacity > 8 then Delta := 16 else
      Delta := 4;
  SetCapacity(FCapacity + Delta);
end;



Если больше 64 элементов — увеличиваем в 1.25 раза, иначе если больше 8, то на 16, иначе на 4. Дешево и хорошо работает.
Статическое переопределение — это не очень хорошо. Лучше динамически, то есть для отдельных векторов. Или иметь возможность вручную увеличить размер.

Мы как-то нарвались на то, что очереди стали настолько большие, что realloc занимал порядка секунды (с использованием свопинга на диск). И чем больше времени занимал realloc, тем больше накапливалось данных (приложение было шибко многонитевое) и раздувало очередь.

В итоге именно для этой очереди пришлось ввести ручное управление.

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


Единственная проблема в том, что написание таких возможностей не было основной целью создания этой библиотеки. Однако я буду рад, если кто-нибудь кинет мне PR, реализующий это. :)

Или иметь возможность вручную увеличить размер.

Не заметил сразу.
Это есть, кстати говоря. См. gvec_resize() и gvec_reserve().

Наверное это немного оффтопик, но разве в таких ситуациях unrolled linked list не будет лучшим решением?

Наша ситуация была в том, что в норме в очереди десяток элементов. Но нам захотелось сделать стресс-тест системы на миллион записей в секунду. И тут узким место стало разрастание очереди.

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

Может и зря не хотели — произвольный доступ все-таки редкость. Но нам приятней было при обработке обращаться с контейнером как с массивом (доступ по индексу). А не заводить итераторы.

оффтоп: а картинка на КДПВ настоящая? Можно узнать, где такой домище находится?
offtop

Не знаю, но картинка судя по всему настоящая. По крайней мере, похожие дома я видел.
Когда она впервые попалась мне на глаза, первой ассоциацией был двухмерный массив. И вот она мне пригодилась.

дом-сороконожка в Москве на ВДНХimage
Я надеялся увидеть механизм замены шаблонов в чистом С, но не увидел. Похоже, что его действительно нет, а жаль.

Простите, если заголовок желтоват — у меня родители журналисты как-никак. :)

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

В С++ же несколько строе кода могут породить мегабайты! С этим, как бы, тоже можно жить — но это уже другой язык с совсем другими свойствами и подходом!

P.S. Котейнеры, кстати, в подобных языках тоже возможны (см. Ada или Java), но с весьма серьёзными ограничениями…
Если использование шаблона порождает буйный рост бинарника, то это либо оптимизатор не справляется, либо честная реализация подобного функционала на чистом си с нуля и в полной мере тоже не будет «стройной».
Очень часто оптимизатор и не может справиться. Просто потому что при написании шаблона вы зачастую не задумываетесь о том, чтобы обледенить код, работающий, скажем, со строками и с целыми числами.

В C — этот код видно. Глазками. И можно переработать реализацию зачастую так, чтобы его стало гораздо меньше. В C++ — его не видно! И нужно производить целые изыскания, чтобы понять откуда что взялось…

Как я сказал: это не упрёк C++ и не похвала C. Это просто объяснение того, где их разработчики сделали принципиально разный выбор.
В таких случаях, всегда хочется спросить: а где у нас есть теория (на этот счёт)?

Чем, там, занимаются специалисты по Computer Science? Когда я был студентом, мне попадались специальные статьи, пестрящие всякими формализмами (теоретико-множественными и категорными), описывающие всякие системы сортов, иерархии типов и т.д. и т.п.

Где же теории, которые позволяют просчитать варианты и определить наилучший (в данных условиях и для данных обстоятельств)?
Чем, там, занимаются специалисты по Computer Science?
Хороший вопрос.

Где же теории, которые позволяют просчитать варианты и определить наилучший (в данных условиях и для данных обстоятельств)?
Теории есть, но не очень понятно как это всё соотносится с реальностью.

То что реально используется — не есть следствие каких-то глубоких теорий, а следует из простых наблюдений над практическим кодом. Возьмите тот же ICF — думаете кто-то копался в категориях? Да нифига: сделали и посмотрели — есть эффект или нету.
теория-то есть. А еще есть затруднения в практической реализации.
Механизм шаблонов C++ можно частично сэмулировать в чистом C при помощи трюка включения хедера без стража.

Идея довольно простая: есть хедер без стража включения, который содержит шаблонный код. Этот хедер предполагает, что параметры шаблона установлены извне в макросы с именами вроде TYPE, SIZE, и т.п.. Далее можно включать этот хедер много раз, каждый раз устанавливая эти макросы по-разному: получится несколько инстанциирований «шаблона».

Пример приведён здесь:
http://stackoverflow.com/a/17670232/556899

Этот механизм никак не мешает MSVC: код можно нормально отлаживать, в нём работает intellisense, и т.п. Естественно, нет никаких проблем с возвращаемыми значениями и указателями на функции. Код безусловно разбухает, но ровно на столько же, насколько разбухает аналогичный код с шаблонами C++.
Код безусловно разбухает, но ровно на столько же, насколько разбухает аналогичный код с шаблонами C++.

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

п.с. никогда не понимал фанатов чистого си, критикующих плюсы. Неужели иметь пару-тройку (сотен) дополнительных инструментов и пользоваться ими — плохо?
Да, мне тоже непонятно, зачем писать на чистом C.
Для тех, кому нужна высокая производительность, особенно полезны в C++ именно шаблоны: это абстракция без потери производительности.

Я думаю, это в основном самодисциплина: ограничивая себя в инструменте, люди настраивают себя на определённый стиль программирования.
Очень часто, когда используется C++, люди вовсю используют контейнеры STL, чтобы не думать о работе с памятью, в результате начинаются тормоза из-за огромного количества аллокаций. Если же ограничить себя чистым C, придётся потратить больше времени на продумывание структур данных в памяти, в результате код наверняка будет работать быстрее.
Так надо же использовать правильно. В качестве простого примера: std::vector::reserve избавит от лишних аллокаций. А вот «честный» аналог std::map за 5 минут не напишешь
UFO just landed and posted this here
то есть ориентироваться вот в таких вот макросных колдунствах вам проще? Или, скажем, у libfftw3 замечательный интерфейс библиотеки, написанный на макросах? И то, что OpenCV и ffmpeg разного масштаба проекты вас тоже не смущает? Или работать со строками/потоками/памятью/чем угодно еще удобнее в си?

В среднем на три строки кода на си нужна лишь одна строка на с++. Не знаю как вы, а я ценю в коде экспрессивность. А если человек, читающий мой код, не знает, что такое std::bind или др. — это вопрос его квалификации.

Вообще, уже сколько раз было: чем проще язык тем более он распространён.

Вы можете нанять одного профессионала или трех студентов. Профессионал на с++ будет знать свой инструмент, а студенты зная си не будут знать как на нем писать. И с распространенностью так же
В среднем на три строки кода на си нужна лишь одна строка на с++. Не знаю как вы, а я ценю в коде экспрессивность.
Ах, вашими бы устами…

Я участовал в проектах где люди использовали C++ ради экспрессивности, но когда я предлагал короткое, буквально однострочное решение — меня просили превратить его в 20 строк, которые «понятнее».

Вот для таких людей С — может быть лучшим вариантом.

А если человек, читающий мой код, не знает, что такое std::bind или др. — это вопрос его квалификации.
А если этот человек — ваш тимлид? И он говорит, что лучше заменить
result = func(...) - 1;
на
if (func(...)) {
  result = 0;
} else {
  result = -1;
}
для «наглядности»? Случай из совсем недавнего прошлого, если что…
result = func(...) — 1;

если { func(...) } -> bool, то лично я предпочту написать через тернарник.
если { func(...) } -> bool, то лично я предпочту написать через тернарник.Но ведь это — длиннее и сложнее! Что случилось с «экспрессивностью», за которую вы ратовали буквально несколькими строками выше? Я уж не говорю о том, что ваш код — дольше работает:
Скрытый текст
bool foo();

int bar() {
  return foo() ? 0 : -1;
}

int baz() {
  return foo() - 1;
}
$ g++ -S -O3 test.cc -S -o-
	.file	"test.cc"
	.text
	.p2align 4,,15
	.globl	_Z3barv
	.type	_Z3barv, @function
_Z3barv:
.LFB0:
	.cfi_startproc
	subq	$8, %rsp
	.cfi_def_cfa_offset 16
	call	_Z3foov
	xorl	$1, %eax
	addq	$8, %rsp
	.cfi_def_cfa_offset 8
	movzbl	%al, %eax
	negl	%eax
	ret
	.cfi_endproc
.LFE0:
	.size	_Z3barv, .-_Z3barv
	.p2align 4,,15
	.globl	_Z3bazv
	.type	_Z3bazv, @function
_Z3bazv:
.LFB1:
	.cfi_startproc
	subq	$8, %rsp
	.cfi_def_cfa_offset 16
	call	_Z3foov
	movzbl	%al, %eax
	addq	$8, %rsp
	.cfi_def_cfa_offset 8
	subl	$1, %eax
	ret
	.cfi_endproc
.LFE1:
	.size	_Z3bazv, .-_Z3bazv
	.ident	"GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
	.section	.note.GNU-stack,"",@progbits
P.S. Можете сходить на godbolt.org и убедиться что для GCC это важно.
Что случилось с «экспрессивностью», за которую вы ратовали буквально несколькими строками выше?

В конкретном примере экспрессивность в другом заключается. В варианте через тернарник мне не надо смотреть, возвращает foo() bool, int или вообще указатель, но я сразу вижу, что bar() может вернуть только одно из двух значений, 0 или -1, соответственно.
Если это так уж хочется увидеть, то можно написать ведь result = bool(func(...)) - 1;. Правда так будет казаться что func — изначально возвращает не bool.

И, главное, как только мы начинаем обсуждать подобные вещи — весь выигрыш от использования мощного языка, на котором писать быстрее — пропадает. Так что почему не выбрать более простой язык где подобных споров будет меньше?
UFO just landed and posted this here
не надо беспокоится что через жалких 10 лет код превратится в тыкву
Что называется «не дождётесь». И в spec2000 и в spec2006 есть программы на C и на C++. Поскольку это тесты, то, понятно, они с тех пор не менялись. Так вот, вы не поверите — но при сборке на современных платформах проблемы возникают с всякими дурацкими программами на C, а не с программами на C++. Потому что они «слишком много знают» и, например, объявляют сами для себя «extern malloc()» прямо посреди функции какой-нибудь.

С++ постоянно усложняется, это же путь в никуда и ни за чем.
C++ усложняется чтобы упростить написание кода. Те же лямбды — штука очень полезная, если их применять вдумчиво. Немного неприятно, что вещь, изобретённая в 60е годы пришла в C++ через полвека после её изобретения, но… Лучше поздно, чем никогда!

Я к плюсам отношусь с большим уважением сейчас, потому что они сделали что обещали: когда C++ сделал поворот в сторону «мегабайт абстракций» в конце прошлого века это было сделано под соусом «оптимизирующий компилятор всё лишнее вычистит» — но это тогда нифига не работало. С современными компиляторами — работает и очень хорошо работает.
UFO just landed and posted this here
Мне проще читать макросы чем шаблоны.

Ключевое слово — «мне». Вы критикуете с++ за то, что они не читаются знанием одного лишь си. А на самом деле у вас попросту нет квалификации в с++.
Не вижу смысла 100500 раз перечитывать код, оно раз оформляется в функцию и потом используется годами в виде опять же одной строчки повсеместно.

Простой пример. У вас есть const char * на входе. Вам нужно дернуть winapi, принимающий LPCWSTR. Напишите интерфейс функции преобразования кодировки на си так, чтобы уместить весь вызов в одну строку и не словить утечку памяти.
UFO just landed and posted this here
У вас есть с++, напишите ядро ОС так чтобы оно работало на голом железе.
Вот, пожалуйста. Там, конечно, не всё на C++, так как не с нуля начинали, но вот вам, к примеру, работа с PCI Express.
Я не хочу забивать голову барахлом специфичным для языка, мне важна программа.

Дык, "сишное барахло" тоже является "барахлом специфичным для языка".

UFO just landed and posted this here
На макросах, и что?

Еще скажите, что читать макросы проще, чем шаблоны

1 строчка или 10 — ничего не меняет по сути, уже давно существуют функции и отдельные файлы.

в одном случае читать одну строку, а в другом — 10. Вот и вся разница. Если не надо ломать голову, что делает одна строка, то она всегда будет понятнее любых десяти.

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

Да и, вообще, все эти «холивары» изначально лишены всякого смысла. Хороший программист напишет хорошую программу на том языке, который он знает. Какой знает, так и знает. Но, уж, если знает, то знает хорошо. Плохой программист не знает ни одного языка и, соответственно, ни на одном языке не сможет ничего путного написать.

Так что, на этом, попрошу всякий «холивар» на Хабрахабре прекратить. Спасибо за понимание. ;-)
UFO just landed and posted this here
А я думал, на любом языке программирования, а те, кто знают только один язык, программистами вообще не являются, только кодерами.
Не бывает. Нельзя вот так просто взять язык, прочитать мануал к нему и начать «фигачить» хорошие программы.

Любой язык требует освоения. Да, у хорошего программиста это получается достаточно быстро, но всё равно не мгновенно.
UFO just landed and posted this here
UFO just landed and posted this here
К списку готовых реализаций я бы добавил еще вот это: https://github.com/nothings/stb/blob/master/stretchy_buffer.h
Причем основные идеи там практически как у вас.

Добавил, спасибо. Однако эта реализация тоже не лишена тех же недостатков, которые разобраны прямо после списка. :)

Большое Вам спасибо за статью, посвящённую такому интересному вопросу. Чуть коротковато. Придётся хорошо проштудировать исходный код, прежде чем задавать конкретные вопросы.

Не могли бы Вы (пока) прояснить смысл пп. 5 («Совместимость между векторами разных типов на уровне присваивания одного другому») и 7 («Максимальная схожесть интерфейса вектора с таковым у std::vector из C++11.»)?

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

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

Благодарю.


Статья изначально предполагалась размера примерно в полтора раза большего, чем она вышла в итоге. Очень хотелось описать ещё следующие вещи:


  • конкретные детали реализации специализирования макросами, в частности то, как именно реализовано указание передачи и возврата по ссылке или по значению;
  • статический и модульный подходы к использованию библиотеки (и их поддержка ею самой);
  • почему последний коммит на момент публикации — это монстр под названием "Полностью изменён интерфейс вектора".

Однако было решено не заниматься переписыванием ReadMe из репозитория. К тому же, для иллюстрации потребовались бы большие куски кода. Это всё раздуло бы статью и сделало бы её менее приятной для чтения, и это при живом-то наличии вроде аккуратного репозитория.


Смысл пункта (5) прост: если типы значений в двух разных векторах имеют одинаковый размер, то мы можем прочитать вектор одного типа как вектор другого, и наоборот. У меня был конкретный пример использования, где это было полезно.


Пункт (7) подразумевает, что вектор ведёт себя аналогично std::vector из C++11, а набор его функций идентичен (пусть и слегка неполон). Я в самом деле сидел с открытым cppreference.com и реализовывал поведение вектора и его хранилища так, как описано там.


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

Описанное Вами — это по сути изменение размера вектора плюс один цикл по элементам. Такую задачу в виде функции можно решить только определив callback присваивания. С другой стороны, более элегантно с точки внешнего вида кода пользователя будет вариант с макросом, которому будут переданы массив назначения и исходный массив. Однако такой вариант будет заметно выбиваться из изначальной концепции "не делаем функции макросами".


Я честно пока не решил, какой из этих вариантов лучше, а поскольку это не входило в необходимый мне (и в итоге реализованный) минимум возможностей, то я оставил это на потом. Быть может, я потом напишу отдельно заголовочные файлы для поддержки подобных вещей. Также были планы дописать отдельно некоторые дополнительные функции для использования этого вектора в качестве динамической строки. Есть намётки и на систему callback'ов, и на итераторы. Но — не сейчас.


Тем не менее, я буду рад появлению в репозитории issue, посвящённых таким вещам, а также pull request'ам.

Очень хорошо. Я Вас понял. И, при возможности, обязательно попытаюсь всё воспроизвести и проверить. Но у меня у самого имеется несколько иная идея, и я её обдумываю (пока).

Я буду рад, если вы поделитесь потом своими размышлениями.

Интересная разработка, у вас получилось очень «по-сишному». Есть в этом языке какая-то суровая красота.
Ну и пару слов в защиту Си по поводу «двадцать первому веку кроссплатформенный ассемблер не нужен» — кроссплатформенных ассемблеров не бывает, это противоречит самому определению ассемблера. Си — это язык, оперирующий основными конструкциями, присутствующими на аппаратном уровне в большинстве современных компьютерных платформ, но скрывающий детали реализации. Именно поэтому он очень хорош для написания ПО встроенных систем, драйверов устройств, некоторых модулей операционных систем, и реальной замены ему в этих задачах — нет (ну или можно на C++ писать как на чистом С, что то же самое).

Спасибо.
А про "кроссплатформенный ассемблер" — это же ирония. Там всё предисловие ею заляпано.

Так Си Таненбаум назвал. Ну и в существовании кроссплатформенного ассемблера, в виде виртуальной машины, с позиции которой он рассматриваивает Си нет ничего удивительного. Грубо говоря — метафора. Кстати в uefi есть байт код машина, не знаю к чему это я, просто интересный факт.
За что не люблю C, так за то, что из трех «быстро», «безопасно», «читаемо/улучшаемо» в большинстве случаев удается выбрать только что-то одно. В то время как в C++ два пункта берутся легко, а зачастую и третий захватывается.
Иногда, очень важно и полезно ответить на вопрос «а что мы можем сделать (на том или ином языке программирования)?».

В результате таких раздумий мог бы появиться, например, новый язык программирования, который можно было бы обозвать «++C» — язык, в котором, при сохранении прозрачной семантики Си, существенно изменён синтаксис таким образом, чтобы обеспечить, например, прозрачную и сквозную типизацию данных путём упрощения языковых конструкций и предоставления специальных средств управления типами (вроде RTTI, атрибутов и рефлексии).
Во-первых, семантически с++ всё-таки проще. Вам может не нравиться синтаксис stl, но с аналогов на чистом си волосы встают дыбом. Во-вторых, если вы посмотрите наиболее популярные предложения к с++17/с++20, то заметите, что язык как раз-таки и движется в сторону упрощения. Ranges, concepts, синтаксический сахар (наподобие fold expressions и structured bindings), всякие constexpr if/for и т.д. значительно упрощают как написание обобщенного кода, так и его использование.
Во-первых, многое определяется предметом разговора. Если речь идёт о сравнительном анализе, то да, о многом можно поговорить. Если интересует довольно узкий вопрос и хочется выяснить пределы совершенства, то… вовсе не обязательно пытаться заставлять волосы вставать дыбом, а попробовать сделать так, как это будет оптимально выглядеть в C.

Во-вторых, я пропустил слишком много серий, и мне было бы крайне любопытно узнать о последних нововведениях. Но, даже, их наличие не может заставить нас отказаться от острых экспериментов над кодом, особенно, если будет очень важен побочный эффект. Кто знает, куда может нас завести жажда познания (и самопознания)?!?
Слов много, а нельзя простейший пример использования привести?
Напрмер: создание массива struct Person, добавление элементов и обход в цикле.
Код примера.
#include <stdlib.h>
#include <stdio.h>

#include "genvector/genvector.h"

typedef struct person_s {
  char Name[32];
  int Age;
} person_t;

GVEC_INSTANTIATE( person_t, person, GVEC_USE_VAL, GVEC_USE_REF );

int main() {
  gvec_person_t family = gvec_person_new(3);

  gvec_person_push( &family, (person_t){
    .Name = "Alice",
    .Age = 30
  } );
  gvec_person_push( &family, (person_t){
    .Name = "Bob",
    .Age = 32
  } );
  gvec_person_push( &family, (person_t){
    .Name = "Kate",
    .Age = 10
  } );

  printf( "%zu\n", gvec_count( family ) );
  while ( gvec_count( family ) > 0 ) {
    person_t member = *gvec_person_back( family );
    printf( "name %s, age %d\n", member.Name, member.Age );
    gvec_pop( family );
  }

  gvec_free( family );
}

Прошу прощения, но не силён в примерах. Целью было именно описание примерного хода мыслей при создании библиотеки.
К тому же, для приведения примеров пришлось бы описывать оба реализованных подхода к использованию библиотеки (статический и модульный), что сильно бы увеличило размер статьи.

1) Примеры очень нужны. И желательно в самой статье. Не обязательно их подробно расписывать. Можно просто спрятать исходник под спойлер — кому нужно, тот почитает.
2) Также хотелось бы пример с доступом к элементам вектора как к элементам обычного массива. Т.е. могу просто сделать family[i].Name = "Batman"?

1) Добавил пример из сообщения выше в статью.


2) Да, но нет, потому что присвоить строковой литерал нельзя — мы в Си. :)
Но можно сделать вот так, да:


strcpy( family[2].Name, "Batman" );
UFO just landed and posted this here
Там производительности не в чем отличаться даже. Особенно с учетом того, что даже к-т роста те же 1.5 что и в большинстве реализаций std::vector
UFO just landed and posted this here

Каюсь, не сравнивал. Но в планах есть.

Sign up to leave a comment.

Articles