HseCppProject

В данном файле (по ссылке) будет конспект семинаров с советами по написанию проекта. При вопросах, предложениях или ошибках писать в тг @CinSGo

Важные пункты

1) Всё в разных файликах. ВАЖНО: надо сделать такую архитектуру, где при изменении одного файлика, другие затрагиваться не будут
2) Лучше реализовывать классами
3) Плюс балл за юнитесты (тесты на все критичные моменты)
Для тестов пишется отдельный CMakeLists

PIPELINE (шаги программы)

1) Прочитать и распарсить аргументы из CLI
2) Прочитать BMP и преобразовать в удобный формат (Сохраняем в какую-то структуру)
3) Создать из распарсенных аргументов последовательность фильтров
4) Применить последовательность к изображению
5) Записать изображение в BMP и сохранить

CLI, прочитать и распарсить аргументы

Parser надо делать независимым от реализации фильтров (т.е. плохо если в парсере написано: читаем аргументы фильтров и сразу создаём их)

ПЛОХО:

if (есть аргумент) {}
if (аргумент == "crop") {...}
// ...
if (аргумент == "gaussian") {...}
// ...

т.к. нарушается неделимость

НАДО: Создать унифицированную структуру, типо

CLI Parser (struct):

ТЕСТЫ

#include <cstdint>

НО могут быть проблемы с множественным инклюдом (наследием, например)

СТАРЫЙ ФИКС:

#ifndef NAME

#include <cstdint>
#include ...

#endif

НОВЫЙ ФИКС:

#progma once

!!!НУЖНА В .h ФАЙЛАХ В САМОЙ ПЕРВОЙ СТРОКЕ!!!

PARSER

Если передали что-то не то или ещё где пользователь накосячил, то мы можем сами вывести что не то:
ЛУЧШЕ НОВЫЙ ФАЙЛ ДЛЯ ВСЕХ СВОИХ ЭКСЕПШИОН

class AppException : public std::exception {
	public:
		consy char *what() const noexception override {
		return "ОШИБКА!!!"
		} // если хотим выводить, то надо переопределить what
};

// !!!В ТАКОМ СЛУЧАЕ ЛУЧШЕ СОЗДАТЬ ОДИН БАЗОВЫЙ КЛАСС И ОТ НЕГО НАСЛЕДОВАТЬСЯ!!!

class InvalidFilterNameException : public AppException2 {
	public:
		InvalidFilterNameException(const std::string& arg_name):
			AppException2("invalid filter name: " + arg_name) {};
};

class AppException2 : public std::runtime_error {
	public:
		AppException2(): std::runetime_error("ОШИБКА В IMAGE_PROCESSOR!!!!") 
		//ошибка как пример
};

int main(int argc, char *arg[]) {
	try {
	// запустить пайплайн (че там надо тебе)
	} catch (AppException& e) {
		// !!!!СВОИ ЭКСЕПШИОНЫ!!!!
	} catch (std::exception& e) {
		std::cout << e.what(); 
		// метод what говорит что произошло, по ошибка (типо what the fuck)
	} catch (...) {
		// какой-то вывод
	}
}
СПОСОБ 2:
#include <unordered_map>

enum class ERROR_CODE {
	OK,
	INVALID_FILTER_NAME,
	INVALID_ARGUMENT_NUMBER
};

std::unordered_map<ERROR_CODE, std::string> error_code_to_message = {
	{ERROR_CODE::OK, ""}
	{ERROR_CODE::INVALID_FILTER_NAME, "invalid filter name"}
	{ERROR_CODE::INVALID_ARGUMENT_NUMBER, "invalid argument number"}
};

ERROR_CODE parse() {
	// ...
}

Image

Фильтры лучше применять к обобщённому (Image)

Также лучше создать класс для Image. В нём есть width, height, vector<vector<Pixel>>

struct Pixel{
	uint_8 R;
	uint_8 G;
	uint_8 B;
}

BMP

1) Чтение и запись бинарных файлов

#pragma pack(push, 1) //ЧТОБ НЕ БЫЛО ВЫРАВНИВАНИЯ ТИПОВ ПО БИТАМ
struct BMPHeader {
	uint16_t id_field;
	uint32_t bpm_size
	//...
};
#pragma pack(pop)

int main(int argc, char *argv[]) {
	std::ifstream input("input.bmp", std::ios::binary);
	BMPHeader bmp_header;
	input.read(reinterpret_cast<char *>(&bmp_header), sizeof(BMPHeader));
	input.seekg(offset, std::ios::beg);
	input.ignore();
	//...
	std::ofstream("output.bmp", std::ios::binary);
	input.write();
	input.seekp();
}

Наследование (для пункта 3 и 4)

class Base {
public:
	Base(int value) : base_value_(value) {}
	
private:
	int base_value_;
};

class Derived : public Base {
public:
	Derived(int derived_value, int base_value)
		: derived_value_(derived_value) {}
private:
//
	int derived_value;
}

// Derived [base fields][derived fields]

Поля в наследниках сохраняются, НО сначала идут унаследованные, а потом личные наследника. Т.е. конструкторы идут от самого общего, к самому частному

ТИПЫ НАСЛЕДОВАНИЯ:

1) Single inheritance (единичное наследование)
Base -> Derived
2) Miltilevel inheritance (множественное наследование)
Base -> Derived -> DDer -> …
3) Multiple (т.е. наследование возможно от нескольких классов)
Base1 + Base2 -> Derived

    class Derived : public Base1, public Base2 {
    public:
	   Derived(int derived_value, int base_value)
		   : Base1(base_value), derived_value_(derived_value) {}
    }

4) Hierinchical Base -> Derived1 and Derived2 5) Hybrid Просто смешанное всё

Связь с типом поля (public, protected, private)

Сверху типы полей Base Слева тип наследования

  public protected
public public protected
protected protacted protacted
private private private

Virtual

class Animal {
public:
	Animal(int value) {
		std::cout << "Animal constructor called with value" << value << std::endl;
	}
};

class Dog: virtual public Animal {  
// !!ВАЖНО!! virtual нужен, т.к. мы в CatDog наследуем Animal **дважды**
// virtual добавляется в родителя, НЕ в прародителя
// virtual надо добавлять ВО ВСЕХ РОДИТЕЛЕЙ, которые унаследуются от одного и того же класса
public:
	Dog(int value): Animal(value) {
		std::cout << "Dog constructor called with value" << value << std::endl;
	}
};

class Cat: virtual public Animal {
// !!ВАЖНО!! virtual нужен, т.к. мы в CatDog наследуем Animal **дважды**
// virtual добавляется в родителя, НЕ в прародителя
// virtual надо добавлять ВО ВСЕХ РОДИТЕЛЕЙ, которые унаследуются от одного и того же класса
public:
	Cat(int value): Animal(value) {
		std::cout << "Cat constructor called with value" << value << std::endl;
	}
};

class CatDog : public Cat, public Dog {
public:
	CatDog(int value) : Animal(value), Cat(value), Dog(value) {
// вызываем конструктор прародителя (т.е. Animal(value)), т.к. из-за virtual конструкторы Cat и Dog игнорируются CatDog
		std::cout << "CatDog constructor called with value" << value << std::endl;
	}	
}

Полиморфизм

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

template <typename T>
T add(T a, T b) {
	return a + b;
}

int add(int a, int b) {
	return a + b;
}

std::string add(std::string a, std::string b) {
	return a + b;
}

int main() {
	add(a, b);
}
class Shape {
public:
  Shape() = default;
  virtual void print_area() const {
    std::cout << "Called Shape print_area" << std::endl;
  }
};

class Rectangle : public Shape {
public:
  Rectangle(int a, int b) : a_(a), b_(b) {};
  void print_area() const override {
    std::cout << "Called Rectangle print_area" << std::endl
    std::cout << "Area is " << a_ * b_ << std::endl;
  }
private:
  // *vptr - указатель на таблицу виртуальных функций
  int a_, b_;
};

class Square : public Shape {
public:
  Square(int a) : a_(a) {};
  void print_area() const override {
    std::cout << "Called Square print_area" << std::endl;
    std::cout << "Area is " << a_ * a_ << std::endl;
  }
private:
  // *vptr - указатель на таблицу виртуальных функций
  int a_;
};


int main() {
  Shape shape;
  shape.print_area();
  Shape *rectangle = new Rectangle(2, 4);
  rectangle->print_area();
  Square square = Square(5);
  Shape &s = square;
  square.print_area();
  Shape square2 = Square(5);
  // object slicing
  square2.print_area();
}

4. Создание из распарсенных аргументов последовательность фильтров

Абстрактный Base Filter virtual Applyu(Imager image) = 0

Тогда наследовать этот класс будут: NegativeFilter , CropFilter , … (т.е. разные фильтры)

vector <BaseFilter*> или vector <unique_ptr<BaseFilters>> тогда для каждого элемента вызываем Apply: filters[i] -> Apply(image);

ТАКЖЕ можно разделить классы на подклассы: т.е. на классы цветов ColorFilter, на классы геометрических обработок (в нём поворот на лево и т.д.)

vector<FilterDescriptor> descriptions; как перевести в vector<unique_ptr<BaseFilter>> filters

Паттерн class CreateFilterFactory Фабрика FuncPtr = unique_ptr<BaseFilter> (*) (vector<stirng>); FuncPtr = std::function<unique_ptr<BaseFilter>(vector<string>); В начале функции map<string //название фильтра//, FuncPtr //указатель на функцию//> mapping;

class CreateFilterFactory {
	FuncPtr = unique_ptr<BaseFilter> (*) (vector<stirng>);
	FuncPtr = std::function<unique_ptr<BaseFilter>(vector<string>);
	//название фильтра и указатель на функцию
	map<string,          FuncPtr> mapping; 
}

5. Записать изображение в BMP и сохранить (записать в output)

ТУТ БУДЕТ ПОПОЛНЕНИЕ ДО 08.03.2026 (ну я постараюсь, правда…) 🥀