LINUX.ORG.RU

По-настоящему важный вопрос

 ,


1

3

Куда вы ставите звёздочки и амперсанды при объявлении/инициализации указателей и ссылок, и почему?

  1. T* name, T& name
  2. T * name, T & name
  3. T *name, T &name
  4. Я талиб, я везде использую передачу по значению

P.S. Это не тупой наброс, мне правда интересно кто как делает, может есть весомые причины делать так или иначе, которые открываются с опытом.

Ответ на: комментарий от soomrack

Например, у тебя много уровней вложенности, которые скрывают основной код, зачем ты их спрятал так глубоко?

Это не я спрятал, это я немного переделал код wandrien-а который выше он приводил. Но в целом уровней не много, всего два. Хотя может как ты предложил было бы и лучше, но дефолтный return в оригинале по двум веткам достигается, это тоже надо учитывать.

firkax ★★★★★
()
Ответ на: комментарий от alysnix

Нет, объявление переменной не создаёт никакой логики в скомпилированной программе. Логику (в т.ч. аллокацию в стеке места) создаёт только её использование. А объявление - это метаданные для последующей корректной компиляции того что ниже.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Оно потом ниже используется,

Да, согласен. Смотрел не внимательно, не увидел. Код, конечно, лапшеобразный. И то, что у n нет осмысленного имени его не улучшает.

Ок, тогда тот же вопрос про const char* name: чем

static void on_create_new(GtkAction *act, FmFolderView *fv) {
    const char* name;
    ...
    
    name = gtk_action_get_name(act);
    ...

    if(!strncmp(name,"NewFolder",9)) {
        templ = NULL;
        prompt = _("Enter a name for the newly created folder:");
        header = _("Creating new folder");
        new_folder = TRUE;
    } else if(G_LIKELY(!strncmp(name,"NewFile",7))) {
        n = atoi(name+7);
        if(n<0 || !(templ=g_list_nth_data(templates,n))) return; /* invalid action name, is it possible? */
    } else if(G_LIKELY(!strcmp(name,"NewBlank"))) {
        templ = NULL;
        prompt = _("Enter a name for empty file:");
        header = _("Creating ...");
    } else { /* invalid action name, is it possible? */
        return;
    }
    
    ...
}

лучше этого

static void on_create_new(GtkAction *act, FmFolderView *fv) {
    ...

    const char* name = gtk_action_get_name(act);
    if(!strncmp(name,"NewFolder",9)) {
        templ = NULL;
        prompt = _("Enter a name for the newly created folder:");
        header = _("Creating new folder");
        new_folder = TRUE;
    } else if(G_LIKELY(!strncmp(name,"NewFile",7))) {
        n = atoi(name+7);
        if(n<0 || !(templ=g_list_nth_data(templates,n))) return; /* invalid action name, is it possible? */
    } else if(G_LIKELY(!strcmp(name,"NewBlank"))) {
        templ = NULL;
        prompt = _("Enter a name for empty file:");
        header = _("Creating ...");
    } else { /* invalid action name, is it possible? */
        return;
    }
    
    ...
}

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

Ivan_qrt ★★★★★
()
Ответ на: комментарий от wandrien

Ну, откуда я мог это знать? «Найди проблему с неймингом». Ну значит та n что вверху и правда внизу не нужна. Но и не вредит т.к. пересечение исключено. Можно было её другое название придумать но я б наверно тоже не стал.

firkax ★★★★★
()
Ответ на: комментарий от soomrack

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

Я пишу в mcedit, и мне несложно написать длинное имя. А вот наблюдать как оно засоряет ценное место на экране - уже сложно.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Ну, откуда я мог это знать?

Вот в том и дело. Код пишется главным образом для чтения людьми и лишь во вторую очередь для исполнения машиной. Тут читаемость не обеспечена.

Если бы код был написан хорошо, то проблема была бы замечена (и исправлена) автором.

wandrien ★★★
()
Ответ на: комментарий от firkax

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

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

alysnix ★★★
()
Ответ на: комментарий от firkax

засоряет ценное место на экране

Слабонервным не читать:

static void windowThreadsRespawn(void)
{
	if (kernelMultitaskerProcessIsAlive(rawEventDispatcherThreadPid)
	&&  kernelMultitaskerProcessIsAlive(windowMaintenanceThreadPid))
		return;

	ACQUIRE_WINDOW_LIST
		return;

	if (!kernelMultitaskerProcessIsAlive(rawEventDispatcherThreadPid))
	{
		rawEventDispatcherThreadPid = kernelMultitaskerSpawnKernelThread(
			rawEventDispatcherThread, "event dispatcher", 0, NULL);
		kernelMultitaskerSetProcessSchedulingClassId(rawEventDispatcherThreadPid,
			kernelMultitaskerGetSchedulingClassUiSubsys());
	}

	if (!kernelMultitaskerProcessIsAlive(windowMaintenanceThreadPid))
	{
		windowMaintenanceThreadPid = kernelMultitaskerSpawnKernelThread(
			windowMaintenanceThread, "window maintenance", 0, NULL);
		kernelMultitaskerSetProcessSchedulingClassId(windowMaintenanceThreadPid,
			kernelMultitaskerGetSchedulingClassUiSubsys());
	}

	RELEASE_WINDOW_LIST
}
wandrien ★★★
()
Ответ на: комментарий от soomrack

Это сильно упрощает работу с малознакомыми библиотеками.

Обычно с библиотеками знакомишься, когда нужны её конкретные функции. И уже в процессе гуглинга узнаёшь, как эти функции использовать. Чем тут поможет автодополнение?

Или там такое автодополнение, что по ним без гуглинга понятно, что зачем и как?

alex1101
() автор топика
Ответ на: комментарий от Ivan_qrt

С name лучше тем, что это «const char*» не засоряет место в строке name = что-то; среди остальных императивных строчек. То, что name имеет тип char const*, я уже запомнил из просмотра заголовков функции (а если вдруг забуду - я точно знаю, куда листать чтоб вспомнить - в начало), а дальше хочу наблюдать чисто алгоритм, и объявления переменных внутри него выглядят мусорно. Особенно если у них длинные типы (const char* то ещё не длинное, а бывают на 30+ букв).

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от alex1101

Или там такое автодополнение, что по ним без гуглинга понятно, что зачем и как?

Именно.

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

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

soomrack ★★★★★
()
Ответ на: комментарий от wandrien

Я наверно не смогу дать чёткий ответ, есть предположения.

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

Во-вторых, как мне кажется, у С++ в целом другой подход к коду - больше внимания человекопонятным объектам и меньше - алгоритмическим подробностям. А поскольку объявления в середине, по моему видению, в первую очередь засоряют именно алгоритм, то в С++ это может быть не настолько критично. В низкоуровневых местах С++ кода, где он похож на Си, я бы тоже применял всё сказанное про стиль Си.

firkax ★★★★★
()
Ответ на: комментарий от firkax

В интерпретаторах обычно вообще нет деклараций.

в каком конкретно интерпретаторе нет «деклараций»? я говорю об интерпретаторах си. которые есть. и они жуют тот же текст, что и компиляторы.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Вот кстати довод против данного аргумента:

#include <iostream>

using namespace std;

typedef char* CharPtr;
// или так:
//using CharPtr = char*;

CharPtr ch0, ch1;

int main()
{
    std::cout << typeid(ch0).name() << '\n' << typeid(ch1).name() << std::endl;
}
char * __ptr64
char * __ptr64

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

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 5)
Ответ на: комментарий от firkax

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

alysnix ★★★
()
Ответ на: комментарий от rumgot

Потому что в строке typedef char* CharPtr ты применил звёздочку для имени «CharPtr», и теперь CharPtr - это тип указателя. Дальше ты его, как тип указателя, можешь применять к переменным. Но провернуть тоже самое без typedef нельзя - звёздочка всегда цепляется к имени, а не у базовому типу. Просто typedef позволяет назначить явное имя производному типу.

firkax ★★★★★
()
Ответ на: комментарий от rumgot

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

Потому что * с т.з. интерпретатора присоединяется не к типу, а к переменной.

int *idx; тут * прилипает к переменной idx, новый тип int * не создается.

А запись typedef char* CharPtr; создает новый тип, это как если бы ты написал (что синтаксис не позволяет) (char *) ch0, ch1;.

PS: Пример – отличный!

soomrack ★★★★★
()
Ответ на: комментарий от alysnix

«Область видимости» - это виртуальное понятие, существующее только внутри компилятора. В получившемся бинарнике никаких областей видимости нет. И, повторюсь, аллокация делается не в момент «выполнения» строки декларации, а либо разово в начале функции на всё, что есть внутри (включая локальные переменные блоков), либо динамически по мере использования декларированных переменных. Сама декларация - чисто метаданные и не более того.

Впрочем, это всё не сильно важно. Важно другое: декларация не является частью полезного алгоритма.

firkax ★★★★★
()
Ответ на: комментарий от alysnix

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

Разве? Это зависит от реализации, требований в стандарте вроде бы нет (не проверял). Аллокация может выполняться, например, только когда переменной присваивается значение, причем эта аллокация может вообще ограничиться регистром процессора и никак не задействовать память.

soomrack ★★★★★
()
Ответ на: комментарий от firkax

Важно другое: декларация не является частью полезного алгоритма.

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

Вот пример, когда объявление переменной удобнее там, где она используется (хотя формально, она определена в самом начале блока { for }):

for (size_t idx = 0; idx < steps_limit; ++idx) ...

soomrack ★★★★★
()

Раз уж тут такая тема, подкину еще для размышлений.

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

int fm_symbol_compare_fast(FmSymbol * s1, FmSymbol * s2)
{
    if (s1 == s2)
        return 0;

    if (s1 == NULL)
        return -1;

    if (s2 == NULL)
        return 1;

    if (s1->value_size - s2->value_size)
        return s1->value_size - s2->value_size;

    return memcmp(s1->value, s2->value, s1->value_size);
}

Вопросы такие:

  1. Всегда ли необходимо максимально DRY-ить код, то есть избавляться от дублей? Или дублирование кода может служить на благо?
  2. Конкретно в данном случае нужно ли убрать дубль выражения s1->value_size - s2->value_size, используя отдельную переменную?
  3. А если бы это был код на Си++?
wandrien ★★★
()
Ответ на: комментарий от alysnix

я говорил про интерпретатор. он работает так как сказал.

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

Вопрос практической сообразности того или иного варианта реализации оставим за скобками.

wandrien ★★★
()
Ответ на: комментарий от wandrien

Зависит от того, используется оптимизатор или нет (да и используется ли эта функция в «нагруженном» коде).

«По хорошему» лучше в коде позаботиться об оптимизации самому.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 3)
Ответ на: комментарий от wandrien

Всегда ли необходимо максимально DRY-ить код, то есть избавляться от дублей? Или дублирование кода может служить на благо?

По-моему, любые принципы не стоит возводить в абсолют. Короткий код можно и продублировать, почему нет.

alex1101
() автор топика
Ответ на: комментарий от wandrien

Я бы написал так:

int fm_symbol_compare_fast(FmSymbol *first, FmSymbol *second)
{
    if (first == second) return 0;
    if (first == NULL) return -1;
    if (second == NULL) return 1;

    if (first->value_size != second->value_size)
        return (first->value_size - second->value_size);

    return memcmp(first->value, second->value, first->value_size);
}

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

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

PS: в if я заменил на !=, т.к. предположил, что это целые числа, и соотв. false будет только когда результат не 0.

soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 2)
Ответ на: комментарий от wandrien

нул это вообще отсутствие всякого символа. равен ли отсутствующие символы друг другу - тут ответ простой. нет. потому что они находятся вне области определения понятия символ.

у вас там что-то с общей логикой.

alysnix ★★★
()
Ответ на: комментарий от soomrack

Ну, кому как. Мне - не удобное, и такие объявления из for я, если предполагаю дальнейшую работу с кодом, переношу наружу всегда. Главная причина та же: это лишнее size_t замусоривает вид алгоритма. Если кстати и вторая (хоть и не сильно часто) - иногда переменная цикла нужна после его окончания чтоб узнать где он остановился.

firkax ★★★★★
()
Ответ на: комментарий от alysnix

У меня вам два ответа.

Первый. NULL == NULL Откройте .c файл, напишите данное выражение и помедитируйте.

Второй. У вас что-то не только с «общей логикой», но еще и с чем-то насчёт «смысла не понимаю, но своё мнение имею». Данный метод - это компаратор для сопоставления ключей в контейнерах типа хэш-таблиц или двоичных деревьев. И нулевой ключ является таким же валидным вариантом, как и прочие.

wandrien ★★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от alysnix

Нет, оно должно возвращать отрицательное число, если первое меньше второго. Но проблема тут есть, да - вычитание может переполниться, и обрабатывать это переполнение будет дороже чем просто сделать две ветки if(<) и if(>).

firkax ★★★★★
()
Ответ на: комментарий от wandrien

У меня другие вопросы:

1) какого типа FmSymbol.value_size ?

2) есть ли надёжных ограничитель диапазона его значений? if() с ранним выходом по ошибке или ассерт если оно не предполагается вообще

int fm_symbol_compare_fast(FmSymbol *s1, FmSymbol *s2) {
    if(s1==s2) return 0;
    if(!s1) return -1;
    if(!s2) return 1;
    if(s1->value_size<s2->value_size) return -1;
    if(s1->value_size>s2->value_size) return 1;
    return memcmp(s1->value, s2->value, s1->value_size);
}
firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от wandrien

В 70-80-е системными вещами тоже не дураки занимались

А при чём тут дураки, когда best practices только на опыте вырабатываются? И Дейкстра свою заметку про goto как раз писал в контексте того как в тогда в мейнстриме было принято писать код.

thunar ★★★★★
()
Последнее исправление: thunar (всего исправлений: 1)