Страницы

Поиск по вопросам

понедельник, 23 декабря 2019 г.

Доступ к protected методам родителя

#ооп #наследование #cpp


Я столкнулся с проблемой в C++, для которой не могу найти нормального решения.
Проблему иллюстрирует следующий пример:

class Parent {
 protected:
  int f() = 0;
};

class Child : public Parent {
 protected:
  int g(Parent& p) {
    return p.f(); // error: 'virtual int Parent::f()' is protected.
  }
  int g(Child& p) {
    return p.f(); // OK.
  }
};


Я хочу, чтобы метод f() был доступен только наследникам класса Parent. В реальной
задаче метод f() виртуальный и наследники его замещают (override), но сути проблемы
это не меняет.

Я знаю 2 плохих решения:


Сделать f() public. В данный момент его и использую. Этот решение плохое, потому
что я не хочу давать доступ к f() кому-либо, кроме наследников.
Добавить всех наследников в друзья родителя. Тут 2 минуса: private члены родителя
становятся доступны наследникам, и при добавлении нового наследника, мы становимся
обязаны обновить базовый класс.


Нашел похожий вопрос на SO, но там никого нормального решения так и не нашлось.
    


Ответы

Ответ 1



Как насчёт такого трюка: делегировать вызов f предку? (Проверка.) class Parent { protected: virtual int f() = 0; int CallFOn(Parent& p) { return p.f(); } }; class Child : public Parent { protected: int g(Parent& p) { return CallFOn(p); // OK. } int g(Child& p) { return p.f(); // OK. // или даже ( http://ideone.com/WLE27X ) // return CallFOn(p); } };

Ответ 2



Наверное легальным способом никак. В качестве более удобного решения можно реализовать дружественный класс-обертку и обращаться к приватным/защищенным методам через него. Думаю что плюсы очевидны: не нужно трогать базовый класс при добавлении дочерних, ну и можно менять реализацию в обертке не трогая дочерних классов, dependency injection и все такое :)

Как правильно обрабатывать и рисовать большой объем данных?

#c_sharp #gdi+ #graphics


Добрый день. 
Дано множество точек (100.000-500.000). Точки образуют кривые(волны), которые отображаются
на графике. Пример графика
Сейчас программа работает в такой последовательности:

Считывание точек из файла
Создание bitmap
Преобразование значений точек в координаты bitmap
Запись точек в bitmap через Graphics.DrawCurve()
Если нужно закрасить часть волны (например, положительную часть) - находиться область
пересечения некоторых областей через Region.Intersect и заливается.
Рисование bitmap

Программа работает медленно.

Подходит ли GDI+ для отображения большого количества данных? Или нужно использовать
что-то другое?
Какая правильная последовательность действий?
Как правильно и в какой момент преобразовывать значения в координаты?
Что почитать?

Спасибо.    


Ответы

Ответ 1



Для подобным манипуляций GDI+ вряд ли подходит. Насколько мне известно, эта технология не работает напрямую с железом, а кроме того, если не ошибаюсь, графические операции просчитываются CPU, а не GPU. За все вышесказанное на 100% не поручусь, однако объем данных , который вы указали, для GDI явно велик. Вам, вероятнее, всего, имеет смысл обратиться к DirectX или OpenGL. Если говорить о C#, то для работы с DX есть ряд оберток, плюс еще XNA (поддержка которого, правда, прекращается)

Ответ 2



Могу Вам посоветовать отбрасывать точки, которые не видны (накладываются друг на друга)в следствии масштаба. Для этого есть алгоритм Дугласа-Пекера.

Ответ 3



В целом можно быстро рисовать на GDI, все как и всегда в компьютерной графике: батчинг и LOD. У вас 500.000 в одной линии? Получается, что нарисованных пикселей меньше, чем точек что вы отдали. Советы, если узкое место это DrawCurves: Надо уменьшить плотность точек на пиксель хотя бы до одного. В вашем случае можно для каждого пикселя выбрать точку (из всех точек которые попадают в этот пиксель), которая по модулю как можно дальше отстоит от предыдущего пикселя. 1 точка на 1 пиксель, как я понимаю, даст все равно гладкую линию, так что можно рисовать просто через DrawLines. Если точки равномерно распределены, то можно выбить каждую вторую или каждую третью. Если результат будет приемлим, то можно оставить =) (применимо в основном для DrawCurves) Прорядить данные и по Y координате: в случае, если точки не отстоят друг от друга по Y координате, то предыдущую можно убрать. Это сделает меньше точек на прямых линиях и больше на изгибах. (Можно комбинировать с 2 пунктом) Насчет закрашивания не особо понятно, приведите пример с ним. Рисовать можно необязательно сначала в битмап. Если процедура быстрая, то можно сразу в OnDraw. Для простых рисовалок на один раз это самое то. Если нужен крутой реалтайм, то я склоняюсь к способам, описанным в этих статьях (тыц и тыц). (Только я бы не советовал сразу браться за них и думать что у вас тоже будет крутой фпс, нужен сначала быстрый способ отрисовки своих данных)

Ответ 4



Выгрузите медленные операции в фоновый поток, и всё будет хорошо.

Парсинг чисел в JSON для сохранения в Core Data

#json #ios #coredata #objective_c #afnetworking


Я расширяю процедуру уже написанного предварительно дампа базы данных на iOS-клиент.
Клиент получает с сервера JSON наполненный значениями. Правильно ли для значений типа
"число" создавать в  Core Data-сущностях атрибут типа NSNumber? В предыдущей реализации
программисты решили передавать все значения (в том числе числовые) типом "строка" и
уже на клиенте разбирать что число а что стринга. Есть ли здесь какой-то подтекст (может
я могу столкнуться с какими-то неудобствами в будущем) или это просто неверная реализация?     


Ответы

Ответ 1



Ответ на первый вопрос: да, правильно - используйте NSNumber. Без вариантов. Ответ на второй вопрос: Желательно, чтобы ваш сервер по API отдавал числа всё-таки в числовом виде ("числа - это числа"), но если есть проблемы с тем, чтобы переделать серверную сторону, вам нужно будет просто "строковые" числа преобразовывать в NSNumber, используя NSNumberFormatter (первый гугл показывает How to convert NSString into NSNumber) Подтекст очень простой - вы тратите дополнительное время на преобразование чисел в строки (на сервере) и строк в числа (на iOS-клиенте). Это немного, но всё же маленький overhead. И вы тратите у себя на клиенте время на написание дополнительных NSNumberFormatter-деклараций. Резюме: если можете, заставьте парней на сервере это всё переделать. Будет чисто и правильно. И небольшое добавление: бывают такие люди, которые очень хорошо знают всякие HTTP-стандарты и REST-стандарты и всякие, в общем, стандарты. Так вот такие люди ответили бы на ваш вопрос точно также, как я, только ещё привели бы штук 10 сокрушительных доводов на 10 абзацев против того, чтобы использовались строки для передачи всего с API-сервера и ещё столько же в пользу передачи чисел. Публикую это добавление в качестве шутки.

Ответ 2



"1" и 1 это не одно и тоже. С другой стороны если вы получаете с сервера словарь или NSArray тип может быть выжен. Используйте для парсинга NSJSONSerialization и если данные с сервера будут корректны проблем быть не должно.

Разработка синтаксического анализатора языка C

#cpp #синтаксический_анализ


На лабе задали написать синтаксический анализатор языка С:


  Написать синтаксический анализатор, обнаруживающий наибольшее число ошибок, для
приведённой ниже грамматики (данная грамматика является упрощённым вариантом грамматики
языка С):


< program >: < type >   ’main’   ‘(‘   ‘)’   ‘{‘   < statement >   ‘}’
< type >: ‘int’
 | ‘bool’
 | ‘void’
: 
  | < declaration > ‘;’
  | ‘{‘ < statement > ‘}’
  | < for >   < statement >
  | < if >      < statement >
  | < return >
< declaration >: < type >   < identifier >   < assign >
< identifier >: < character >< id_end >
< character >: ‘a’ | ‘b’ | ‘c’ | ‘d’ | ‘e’ | ‘f’ | ‘g’ | ‘h’ | ‘i' | ‘j’ | ‘k’ |
‘l’ | ‘m’ | ‘n’ | ‘o’ | ‘p’ | ‘q’ | ‘r’ | ‘s’ | ‘t’ | ‘u’ | ‘v’ | ‘w’ | ‘x’ | ‘y’ |
‘z’ | ‘A’ | ‘B’ | ‘C’ | ‘D’ | ‘E’ | ‘F’ | ‘G’ | ‘H’ | ‘I’ | ‘J’ | ‘K’ | ‘L’ | ‘M’ |
‘N’ | ‘O’ | ‘P’ | ‘Q’ | ‘R’ | ‘S’ | ‘T’ | ‘U’ | ‘V’ | ‘W’ | ‘X’ | ‘Y’ | ‘Z’ | ‘_’
  :
| 
:
  | ‘=’ 
: 
  | 
: 
: ‘0’ | ‘1’ | ‘2’ | ‘3’ | ‘4’ |  ‘5’ | ‘6’ | ‘7’ | ‘8’ | ‘9’
:
  | 
: ‘for’ ‘(‘  ‘;’  ‘;’ ‘)’
:        
  |           
:  ‘<’ | ‘>’ | ‘==’ | ‘!=’
: ‘if’ ‘(‘  ‘)’
: ‘return’  ‘;’


Первые 4-е строки вроде понял(это описание мейна). А что идет дальше? Какие варианты
кода может реализовать данный шаблон? Может у кого уже есть какие-то наработки на языке
С++? В сети ничего не нашел(т.к. не знаю что конкретно мне нужно, тупо как-то проанализировать
код?). В любом случае пример кода не помешал бы.
    


Ответы

Ответ 1



@Alerr, исходники yacc или bison Вам сейчас совершенно не помогут. А вот Вирта почитайте. Еще можете поискать исходники "калькуляторов". Они довольно распространены и обычно там для трансляции выражения, например, в обратную польскую нотацию применяют как раз алгоритм рекурсивного спуска (он тут тоже подойдет). Думаю, рекурсивный разбор как раз будет в следующей лабе. В принципе ничего сложного (здесь Вам уже сказали об этом) тут нет. Для начала напишите лексический анализатор. Это функция, которая читает ввод и возвращает лексему - структуру, в которой описано что мы прочли идентификатор, ключевое слово, число, знак операции, скобку и т.п. При этом часть грамматики у Вас уйдет в этот лексический анализатор и грамматика упростится. Далее, просто пишете набор функций в соответствии с грамматикой. Ну, пожалуй надо придумать, как сообщать об ошибках, принять решение, заканчивать разбор при первой же ошибке или пробовать "восстановиться" и разбирать дальше (это сложнее) и т.п. Я начну какой-то сильно псевдокод, думаю станет понятней int programm () { int rc; lexem_t lx = get_lexem(); if (lx.type == KEYWORD && (lx.subtype == INT || lx.subtype == VOID || lx.subtype == BOOL)) { lx = get_lexem(); if (lx.type == IDENT && strcmp(lx.value, "main") == 0) { // очевидно, в нормальном Си тут нужно вызывать разбор списка параметров // но в нашей грамматике должна идти пара '(' ')' if ((rc = parentheses()) == OK) { lx = get_lexem(); if (lx.type == SPEC && lx.char_value == '{') if ((rc = statement()) == OK) { // уффф! почти добрались до конца lx = get_lexem(); if (lx.type == SPEC && lx.char_value == '}') return OK; // Ура!!! rc = error_message(lx, ...); // ждали '}', а прочли ??? } return rc; } rc = error_message(lx, ...); // ждали '{', а прочли ??? } return rc; // далее в том же духе обработка ошибок, просто в этом "редакторе" мало строк помещается, набирать с indent трудно ... } // конец if (lx.type == KEYWORD ... return error_message(lx, ....); // ждали int | void | bool, а прочли ??? } Надеюсь, в общих чертах понятно. Еще одно, Вам почти наверняка понадобится функция аналогичная ungetc(c), но для лексемы. Не забудьте учесть, продумывая структуры данных.

Ответ 2



Здесь простая грамматика языка, которую вполне можно реализовать без использования lex и yacc. Для лексического анализа нужен лексический анализатор, который по набору символов будет определять, какая именно лексема встретилась. Синтаксический анализатор, опираясь на поток лексем, может проверять синтаксис. Лексический анализ В данном конкретном случае достаточно рассматривать один символ из входного потока, для того, чтобы понять, о какой лексеме идёт речь. Такой алгоритм называется анализом с предпросмотром на 1 символ. Обычно лексический анализатор пропускает все разделители (пробелы и символы новой строки), которые встречает во входном потоке, и не возвращает их в виде лексем. Для начала определим, какие лексемы перечислены в грамматике. Это ключевые слова: main, int, bool, void, for, if, return разделители и операторы {} () ; = < > == != а также идентификаторы и числа. Для упрощения кода мы добавим также лексему, которая будет соответствовать концу файла. Кроме того, если мы не можем распознать лексему, мы также должны вернуть специальный код. Итого: #define MAIN 1 #define INT 2 #define BOOL 3 #define VOID 4 #define FOR 5 #define IF 6 #define RETURN 7 #define LBRACE 101 /* { */ #define RBRACE 102 /* } */ #define LPAREN 103 /* ( */ #define RPAREN 104 /* ) */ #define SEMICOLON 105 /* ; */ #define ASSIGN 106 /* = */ #define LT 107 /* < */ #define GT 108 /* > */ #define EQUAL 109 /* == */ #define NOT_EQUAL 110 /* != */ #define IDENTIFIER 201 #define NUMBER 202 #define END 301 /* конец файла */ #define ERROR 302 /* ошибка */ Что такое лексический анализатор? В простейшем случае это одна функция, которая получает на вход поток символов, читает оттуда лексему и возвращает её. Для лексем IDENTIFIER и NUMBER нам потребуются также прочитанные символы, поэтому мы должны передавать адрес буфера для сохранения символов из входящего потока. Для того, чтобы прочитать несколько лексем, нужно вызвать функцию несколько раз. То есть сигнатура функции должна быть такой: int get_lexem(FILE* stream, char* buffer); Как её реализовать? Рассмотрим входной поток из символов "a=534", для упрощения пусть она будет без пробелов. Прочитав из потока символ '=' мы не можем остановиться, потому что в потоке может находиться лексема "==". Значит, мы читаем следующий символ, им оказывается '5' и теперь мы точно значем, что прочитали лексему ASSIGN а не EQUAL. Однако, символ '5' уже прочитан из потока, и при следующем вызове мы прочитаем оттуда уже '3' и '4'. Нам совершенно точно нужно сохранять между вызовами get_lexem последний прочитанный символ. Это можно делать разными способами, мы будем возвращать символ обратно в поток, вызывая стандартную функцию C ungetc. Такая ситуация будет возникать не каждый раз, а только тогда, когда лексема содержит больше одного символа. int get_lexem(FILE* stream, char* buffer, size_t buffer_length) { int ch = getc(stream); size_t i; /* пропускаем все пробелы, табуляции и символы новой строки */ while(isspace(ch)) ch = getc(stream); /* конец файла */ if(ch == EOF) return END; /* простые односимвольные лексемы */ if(ch == '{') return LBRACE; if(ch == '}') return RBRACE; if(ch == '(') return LPAREN; if(ch == ')') return RPAREN; if(ch == ';') return SEMICOLON; if(ch == '<') return LT; if(ch == '>') return GT; /* сложный случаай 1: = или == */ if(ch == '=') { ch = getc(stream); if(ch == '=') return EQUAL; ungetc(ch, stream); return ASSIGN; } /* сложный случай 2: != */ if(ch == '!') { ch = getc(stream); if(ch == '=') return NON_EQUAL; /* по грамматике сразу после ! обязан идти символ = */ /* если это не так, в лексеме ошибка */ return ERROR; } /* сложный случай 3: идентификатор или ключевое слово */ if(isalpha(ch) || ch == '_') { i = 0; do { buffer[i++] = ch; } while((ch = getc(stream)) != EOF && (isalpha(ch) || isdigit(ch) || ch == '_')); if(ch != EOF) ungetc(ch, stream); buffer[i] = '\0'; if(strcmp("main", buffer) == 0) return MAIN; if(strcmp("int", buffer) == 0) return INT; if(strcmp("bool", buffer) == 0) return BOOL; if(strcmp("void", buffer) == 0) return VOID; if(strcmp("for", buffer) == 0) return FOR; if(strcmp("if", buffer) == 0) return IF; if(strcmp("return", buffer) == 0) return RETURN; return IDENTIFIER; } /* сложный случай 4: число */ if(isdigit(ch)) { i = 0; do { buffer[i++] = ch; } while((ch = getc(stream)) != EOF && isdigit(ch)); if(ch != EOF) ungetc(ch, stream); buffer[i] = '\0'; return IDENTIFIER; } } Обратие внимание, как собираются символы лексем IDENTIFIER и NUMBER. Логика тут сложная, но каждый отдельный момент мы уже обсуждали. Если в потоке, например, находятся символы "_abc123=", то функция прочитает их все, включая '=', при этом символы "_abc123" попадут в буфер, а '=' мы вернём назад в поток. Конец файла возвращать в поток не нужно. Поскольку ключевые слова невозможно отличить от идентификаторов по правилам грамматики, а можно только по словарю, мы опеределяем их там же, где и идентификаторы. В приведённом коде есть две проблемы. Первая из них связана с тем, что возможно переполнение буфера. Скажем, мы сделаем размер буфера равным 1000 символов, а безумный программист придумает идентификатор длиной 1002 символа. Код и так получился довольно большим, поэтому я не стал писать обработку этой ошибки. Вторая проблема быстродействия, поскольку при чтении каждого идентификатора мы прогоняем его по списку ключевых слов самым медленным способом. В реальных компилятора здесь используют хеш-таблицы. Синтаксический анализ Простейший ручной способ синтаксического анализа — алгоритм рекурсивного спуска, который позволяет разбирать грамматики LL(1). Ваша грамматика как раз такая, что упрощает нам задачу. Правила кодируются очень простым способом. Например, что такое программа? program :: = type "main" "(" ")" "{" statement "}" Вот как это кодируется в C: char buffer[1024]; int program(FILE* stream) { int statement_result; int lexem = get_lexem(stream, buffer); if(lexem != INT || lexem != BOOL && lexem != VOID) return UNRECOGNIZED_MAIN_TYPE; lexem = get_lexem(stream, buffer); if(lexem != MAIN) return EXPECTED_MAIN; lexem = get_lexem(stream, buffer); if(lexem != LPAREN) return EXPECTED_LPAREN; lexem = get_lexem(stream, buffer); if(lexem != RPAREN) return EXPECTED_RPAREN; lexem = get_lexem(stream, buffer); if(lexem != LBRACE) return EXPECTED_LBRACE; statement_result = statement(stream); if(statement_result != OK) return statement_result; lexem = get_lexem(stream, buffer); if(lexem != RBRACE) return EXPECTED_RBRACE; return OK; } Многословно, но в принципе понятно. Константы OK, UNRECOGNIZED_MAIN_TYPE, EXPECTED_MAIN необходимо определить самостоятельно. Они описывают ошибки, которые грамматика может распознать. UNRECOGNIZED_MAIN_TYPE означает, что анализатор ожидает один из трёх типов, но получил что-то другое. Константа OK означает, что ошибок нет. Конструкция lexem = get_lexem(stream, buffer); if(lexem != RBRACE) return EXPECTED_RBRACE; встречается очень часто. Её можно назвать "требовать наличия лексемы во входном потоке". Для неё удобно завести отдельные функции: bool require_lexem1(FILE* stream, int expected1) { int lexem = get_lexem(stream); return lexem == expected1; } bool require_lexem2(FILE* stream, int expected1, int expected2) { int lexem = get_lexem(stream); return lexem == expected1 || lexem == expected2; } bool require_lexem3(FILE* stream, int expected1, int expected2, int expected3) { int lexem = get_lexem(stream); return lexem == expected1 || lexem == expected2 || lexem == expected3; } Теперь функцию program можно сделать проще и короче: int program(FILE* stream) { int statement_result; if(!require_lexem3(stream, INT, BOOL, VOID)) return UNRECOGNIZED_MAIN_TYPE; if(!require_lexem1(stream, MAIN)) return EXPECTED_MAIN; if(!require_lexem1(stream, LPAREN)) return EXPECTED_LPAREN; if(!require_lexem1(stream, RPAREN)) return EXPECTED_RPAREN; if(!require_lexem1(stream, LBRACE)) return EXPECTED_LBRACE; statement_result = statement(stream); if(statement_result != OK) return statement_result; if(!require_lexem1(stream, RBRACE)) return EXPECTED_RBRACE; return OK; } Функция statement сложнее и интереснее. Она на основании следующей лексемы принимает решение о том, какое из альтернативных правил применить. int statement(FILE* stream) { int statement_result; int lexem = get_lexem(stream); if(lexem == INT || lexem == BOOL || lexem == VOID) return declaration(stream); if(lexem == LBRACE) { statement_result = statement(stream); if(statement_result != OK) return statement_result; if(!require_lexem1(stream, RBRACE)) return EXPECTED_RBRACE; return OK; } if(lexem == FOR) return for_rule(stream); if(lexem == IF) return if_rule(stream); if(lexem == RETURN) return return_rule(stream); return UNRECOGNIZED_STATEMENT; } Эта функция рекурсивна, поэтому позволяет распознавать сложные конструкции вида { { { int i = 300; } } }. Для примера реализуем также функцию declaration: int declaration(FILE* stream) { if(!require_lexem1(stream, IDENTIFIER)) return EXPECTED_IDENTIFIER; if(!require_lexem1(stream, ASSIGN)) return EXPECTED_ASSIGN; if(!require_lexem2(stream, IDENTIFIER, NUMBER)) return EXPECTED_IDENTIFIER_OR_NUMBER; if(!require_lexem1(stream, SEMICOLON)) return EXPECTED_SEMICOLON; return OK; } Стараемся придерживаться принципа: для каждого правила в грамматике писать отдельную функцию, так удобнее проводить соответствия. В приведённом коде я отошёл от этого принципа, чтобы сделать код чуть короче, в частности, правила assign и assign_end попали внутрь declaration. В результате алгоритм спускается от верхнего общего правила к нижним (от program к declaration через statement), при этом некоторые функции могут вызывать друг друга рекурсивно. Именно поэтому алгоритм называется рекурсивным спуском. Реализовав все правила, мы получим полноценный анализатор программ, который сможет сообщать нам об ошибках. Напишем предпоследнюю функцию, которая превратит коды ошибок в текстовые сообщения: const char* get_message(int code) { switch(code) { case OK: return "Ok"; case UNRECOGNIZED_MAIN_TYPE: return "Unrecognized main type"; . . . } return "Unrecognized code"; } Главная функция программы оказывается очень простой: void main() { int code = program(stdin); const char* message = get_message(code); printf("%s\n", message); } Заключение Мы реализовали лексический анализатор (get_lexem), набор вспомогательных функций для проверки наличия лексем (require_lexem1, require_lexem2, require_lexem3) и набор функций для каждого правила грамматики (program, statement, declaration и множество других, которые вам предстоит написать самостоятельно). Функция get_message позволила нам выводить понятный текст вместо непонятных кодов. У нас получилось два набора констант: коды лексем (LPAREN, IDENTIFIER и т.д.) и коды ошибок (OK, EXPECTED_ASSIGN и т.д.) Вся программа целиком способна проверить код другой программы и вывести OK, если она соответствует грамматике. В противном случае она выводит сообщение об ошибке.

C++: Undefined reference при линковке теста с либой

#cpp #шаблоны_с++ #компоновщик #clang


Вот исходник тесты

#include 
#include 
using namespace RFw;

int main() {
    Array _a_;

    _a_.resize(5);

    _a_[2] = 'b';
    _a_[3] = 'a';

    printf("%c%c\n", *(_a_.provideElement(2)), *(_a_.provideElement(3)));

    return 0;
}

Так компилю:

c++ test.cpp -o test -std=c++11 -Isrc-core/ -Lbin/ -lcore

Вывод:

/tmp/test-k6aFM8.o: In function `main':                                         
                                                                                  
                                                 
test.cpp.text+0x27): undefined reference to `RFw::Array::Array(unsigned int,
int*)'                                                                            
                                              
test.cpp.text+0x3a): undefined reference to `RFw::Array::resize(unsigned int)'
                                                                                  
                                            
test.cpp:(.text+0x52): undefined reference to `RFw::Array::operator[](unsigned
int)'                                                                             
                                              
test.cpp:(.text+0x76): undefined reference to `RFw::Array::operator[](unsigned
int)'                                                                             
                                              
test.cpp:(.text+0x9a): undefined reference to `RFw::Array::provideElement(unsigned
int)'
test.cpp:(.text+0xbd): undefined reference to `RFw::Array::provideElement(unsigned
int)'
test.cpp:(.text+0x101): undefined reference to `RFw::Array::~Array()'
test.cpp:(.text+0x11b): undefined reference to `RFw::Array::~Array()'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

PS Array взят из Array.hpp который инклюдится в common.hpp
Array.hpp:

/**
 *      src-core/Array.hpp
 */
#ifndef _ARRAY_HPP_
#define _ARRAY_HPP_

#include "util.hpp"
#include "Object.hpp"
#include "arrayExceptions.hpp"

namespace RFw {
    template
    class Array : public Object {
    public:
                                Array(intnum _length = 0, TValue* _array = nullptr);
        virtual                 ~Array();

        void                    addToTop(const TValue _element) throw(ArrayOverflowException);
        void                    addToEnd(const TValue _element) throw(ArrayOverflowException);
        void                    clone(const intnum _length, const TValue* _array);

        TValue*                 provideElement(const intnum _index) throw(IndexOutOfRangeException);
        const TValue*           provideElement(const intnum _index) const throw(IndexOutOfRangeException);

        TValue&                 operator[](const intnum _index) throw(IndexOutOfRangeException);
        const TValue&           operator[](const intnum _index) const throw(IndexOutOfRangeException);

        void                    resize(const intnum _newLentgh);

        const Array     getClone() const;
        const intnum            getLength() const;
        const TValue*           getArray() const;
    private:
        intnum                  length__;
        TValue*                 array__;
    };
}

#endif //   _ARRAY_HPP_
    


Ответы

Ответ 1



С шаблонами вы должны помещать реализацию методов в header. В вашем случае пишите так: template class Array : public Object { public: Array(intnum _length = 0, TValue* _array = nullptr) : length__(_length), array__(new TValue[_length]) { // остаток имплементации } virtual ~Array() { delete[] array__; } // и так далее }; Если вы вынесете реализацию методов в .cpp-файл, компилятор не будет знать, какие инстанциации шаблона ему нужно компилировать. Это ограничение модели компиляции языка C++. Sorry for this. Небольшое объяснение. В C++ шаблон не есть класс, это лишь «описание», по которому при подстановке шаблонных аргументов генерируется реальный класс. Никакого объектного кода шаблон сам по себе не производит. Когда компилятор компилирует C++-файл с кодом, использующим шаблон, он видит, с каким шаблонным аргументом используется этот шаблон. Но когда он компилирует C++-файл с реализацией шаблонных методов, он не знает, с какими аргументами инстанциировался шаблон в других файлах, ведь каждый C++-файл компилируется по отдельности! Поэтому решением является положить реализацию внутрь шаблонного класса. При этом компилятор в той точке, где он видит инстанциацию шаблона, видит весь его код, и может скомпилировать все методы класса. (Существует ещё уродливый костыль с указанием в C++-файле точного списка инстанциаций шаблона, который настолько ужасен, что я не хочу о нём и говорить. Аналогом этому было бы около каждой функции указывать заранее список всех аргументов, с которым функция будет вызвана в программе.)

Вывод файлов с определённым расширением

#python


У меня есть функция, которая выводит двумерный список файлов с определённым расширением:

 def selectfiles(self, dirs):

       return [filter(lambda i: '.hdf' in i  in i , os.listdir(dirs[x])) for x in
                                                                  range(len(dirs))]  


Подскажите, пожалуйста, как её лучше модифицировать, чтобы она выводила не только
названия файлов, но и пути к ним?

Т.е. сейчас:

 [[tem1.txt, tem2.txt, tem3.txt],[tem10.txt, tem11.txt]]


А нужно:

 [[home/test/tem1.txt, home/test/tem2.txt, home/test/tem3.txt],[home/test10/tem10.txt,
home/test10/tem11.txt]]

    


Ответы

Ответ 1



Итого, функция из комментария: @staticmethod def selectfiles(dirs): return [map(lambda i: dirs[x] + '/' + i, filter(lambda i: '.hdf' in i, os.listdir(dirs[x]))) for x in range(len(dirs))] Этот же алгоритм, но pythonic way: @staticmethod def select_files2(dirs): files = set() for dir in dirs: for file in os.listdir(dir): if file.endswith('.hdf'): files.add(os.path.join(dir, file)) return [files]

Ответ 2



Вместо selectfiles(dirs) функции, которая принимает список директорий, лучше создать функцию getfiles(dirname), которая принимает одну директорию и использовать map(), чтобы применить её к списку dirs всех директорий: list2D = map(getfiles, dirs). Чтобы получить из директории все файлы с заданным расширением: import os from glob import glob def getfiles(dirname): return glob(os.path.join(dirname, '*.hdf')) Если dirname не содержит файловых шаблонов, то это равнозначно: def getfiles(dirname): return [os.path.join(dirname, filename) for filename in os.listdir(dirname) if filename.endswith('.hdf')] В более общем случае (когда задан произвольный шаблон для имени файла), можно использовать glob.glob1(), fnmatch.filter() функции. Или с помощью pathlib модуля: from pathlib import Path list2D = [list(Path(dirname).glob('*.hdf')) for dirname in dirs]

Удаление общих подвыражений

#алгоритм #cpp


Доброго времени суток!
Столкнулся с такой задачей: есть n уравнений, записанных в символьном виде, необходимо
оптимизировать количество операций для их вычисления (пока для простоты подразумевается
только сложение). Вот пример:
Исходная система уравнений:
x1 = a + b + c
x2 = a + d + c
x3 = a + e + c

В результате должно получиться следующее:
r1 = a + c
x1 = r1 + b
x2 = r1 + d
x3 = r1 + e

Здесь r1 - это замена повторяющейся операции a + b. Легко видеть, что количество
сложений уменьшилось с 6 до 4. Все это хорошо, но с маленькими уравнениями, а вот когда
количество переменных переваливает за несколько сотен - надо писать программу.
Реализовывать алгоритм мне необходимо на C/C++. 
После длительного гугления, я выяснил что это называется common subexpression elimination
(или удаление общих подвыражений). Однако, как написать программу или каким именно
методом такое можно реализовать я так и не понял. И поэтому придумал свой велоспед. 
Суть велосипеда в следующем:
Входными данными является набор строк. Каждая строка представляет собой одно уравнение,
например: abc - это x1 = a + b + c
Алгоритм действий следующий:
1) упорядочиваем все переменные всех уравнений (ибо он могут быть записаны не по порядку)
2) создаем для каждого уравнения битовый вектор
3) Используя побитовый AND складываем все уравнения и ищем вектор с максимальным
количеством установленных бит
4) Добавляем новое уравнение составленное из бит полученных на предыдущем шаге
5) Повторяем все шаги пока не получим нулевой вектор
Я сильно сомневаюсь, что иду правильным путем.
Подскажите, пожалуйста, может кто-то сталкивался с подобными алгоритмами, или натолкнете
на идею/книгу/библиотеку?
P.S. Сорри если сумбурно, я уже совсем замучался((    


Ответы

Ответ 1



Смотрите, это делается, например, так. Для начала, для преобразований кода вам нужно распарсить его в syntax tree, или в ещё более удобное представление (например, semantic tree). (Это самая лёгкая часть, вам всё равно это понадобится.) Суммы переведите из бинарных в n-арные операции. Если одно из слагаемых — сумма, уберите его и добавьте его подслагаемые. То же с разностями. Установите канонический порядок на слагаемых, чтобы не различать x + 3 и 3 + x. Составляете список всех подвыражений в дереве путём его рекурсивного обхода. Далее вам нужно ввести порядок на выражениях. Например, вы можете написать хэш-функцию такую, чтобы одинаковые подвыражения обязательно получали одинаковое значение. (Если вы определите хэш-функцию рекурсивно, будет как раз то, что надо.) Вычислите значения хэша на каждом подвыражении (опять-таки рекурсивно). Отсортируйте список по хэшу. Рассматривайте (относительно маленькие) группы с одинаковым хэшем. Определите рекурсивно равенство выражений: высоты деревьев не равны => подвыражения не равны типы корней не совпадают => подвыражения не равны поддеревья не совпадают => подвыражения не равны иначе равны Таким образом, получите список одинаковых подвыражений. Поскольку суммы можно разделить на части по-разному, вычисляйте хэши также для частичных сумм. Достаточно отсортировать слагаемые, и вычислить 2^n вариантов (включая/не включая каждое из слагаемых). Эти частичные суммы тоже добавляйте в список подвыражений (со ссылкой на оригинальное выражение). При разумном числе слагаемых получится не так уж и много вариантов. Дерзайте, у вас прекрасное задание!