Bad practice. Возврат ошибки. Кастомная структура
#новичкам
Если нам запрещают кидать исключения, то надо как-то сообщать об ошибке. И самый прямолинейный способ это сделать - вернуть ошибку в качестве возвращаемого значения. Но как это сделать, если функция при успешном выполнении должна возвращать нормальное значение?
Обернем это все в класс и сделаем его типом возвращаемого значения!
Без шаблонной магии это выглядит примерно так. 2 условных поля - валидный результат и сообщение об ошибке(код ошибки). Ну и немного полезных методов для красивой инициализации результата.
Этот подход работает, но у него есть несколько весомых недостатков:
🔞 В структуре хранится всегда 2 поля, хотя семантически должно хранится что-то одно. Возвращается либо ошибка, либо валидный результат. Нет суперпозиции. А в коде выше есть. Как минимум это увеличивает размер объекта, а как максимум(при ошибочной реализации и/или использовании, но все же) приводит к той самой суперпозиции, когда есть и ошибка и результат.
🔞 Так как всегда конструируются и результат, и ошибка, то ничто не мешает использовать результат без проверки, вернула ли функция ошибку.
В общем, классы результатов, где 2 поля, но только одно из них всегда актуальное - не очень, так делать не нужно. Такая практика ведет к ошибкам и синтаксически и семантически никак не защищает пользователя от неправильного использования. Есть варианты получше.
You can do better. Stay cool.
#badpractice
#новичкам
Если нам запрещают кидать исключения, то надо как-то сообщать об ошибке. И самый прямолинейный способ это сделать - вернуть ошибку в качестве возвращаемого значения. Но как это сделать, если функция при успешном выполнении должна возвращать нормальное значение?
Обернем это все в класс и сделаем его типом возвращаемого значения!
template<typename T>
struct Result {
T value;
std::string error;
static Result ok(T val) {
return Result{std::move(val), {}};
}
static Result fail(std::string err_msg) {
return Result{T{}, std::move(err_msg)};
}
operator bool() const { return error.empty(); }
};
Result<double> safe_divide(double a, double b) {
if (b == 0.0) {
return Result<double>::fail("Division by zero");
}
return Result<double>::ok(a / b);
}
auto div_result = safe_divide(10.0, 2.0);
if (div_result) {
std::cout << "Result: " << div_result.value << std::endl;
} else {
std::cout << "Error: " << div_result.error << std::endl;
}
Без шаблонной магии это выглядит примерно так. 2 условных поля - валидный результат и сообщение об ошибке(код ошибки). Ну и немного полезных методов для красивой инициализации результата.
Этот подход работает, но у него есть несколько весомых недостатков:
🔞 В структуре хранится всегда 2 поля, хотя семантически должно хранится что-то одно. Возвращается либо ошибка, либо валидный результат. Нет суперпозиции. А в коде выше есть. Как минимум это увеличивает размер объекта, а как максимум(при ошибочной реализации и/или использовании, но все же) приводит к той самой суперпозиции, когда есть и ошибка и результат.
🔞 Так как всегда конструируются и результат, и ошибка, то ничто не мешает использовать результат без проверки, вернула ли функция ошибку.
В общем, классы результатов, где 2 поля, но только одно из них всегда актуальное - не очень, так делать не нужно. Такая практика ведет к ошибкам и синтаксически и семантически никак не защищает пользователя от неправильного использования. Есть варианты получше.
You can do better. Stay cool.
#badpractice
👍25❤11🔥6
Доступ к приватным членам. Макросы
#новичкам
Доступ к приватным членам? Фуфуфу, это грязь! Да как вы смеете?! Хорошие люди старались, инкапсуляцию изобретали, а вы надругаться над ними хотите? Не по-славянски это, не по-православному...
Не далеки от правды слова выше. Если у вас уже есть какой-то работающий класс и вы хотите ужом извернуться, чтобы вытащить его кишки наружу - надо задуматься. О степени своей маниакальности, но главное - над архитектурой вашего кода. Потому что в большинстве случаев вы будете делать какое-то безобразие и разного рода хаки, чтобы подсмотреть в приватные поля. Лучше чуть подольше подумать и переработать целиком решение с учетом новых вводных.
Но!
Врага надо знать в лицо!
Поэтому в течение нескольких следующих постов мы будем обсуждать варианты инвазивного и неинвазивного доступа к приватным членам класса. Как говорится: не повторяйте в проде, чревато говнокодом по теории разбитых окон.
И на завтрак мы разберем самый простой способ. Макросы.
Есть у нас хэдэр:
Подключаем его в цппшник, но перед этим делаем грязь:
Строчкой
И это работает! Да, стандартом запрещено заменять макросами ключевые слова. Но это вообще не волнует компиляторы. gcc даже c флагами -pedantic -Wall не выдает никаких предупреждений. clang только с флагом -pedantic генерирует варнинг.
Конечно же за такой макрос надо не то что по рукам бить. Надо их из жопы вырывать без анастезии и вставлять в нормальное место.
Пожалуй, это самый дурнопахнущих из всех способов, потому что ломает инкапсуляцию прям везде. Так сказать начали с вкуснятины. Но оставайтесь на свзяи, продолжение тоже будет вкусным.
Be legal. Stay cool.
#NONSTANDARD #badpractice
#новичкам
Доступ к приватным членам? Фуфуфу, это грязь! Да как вы смеете?! Хорошие люди старались, инкапсуляцию изобретали, а вы надругаться над ними хотите? Не по-славянски это, не по-православному...
Не далеки от правды слова выше. Если у вас уже есть какой-то работающий класс и вы хотите ужом извернуться, чтобы вытащить его кишки наружу - надо задуматься. О степени своей маниакальности, но главное - над архитектурой вашего кода. Потому что в большинстве случаев вы будете делать какое-то безобразие и разного рода хаки, чтобы подсмотреть в приватные поля. Лучше чуть подольше подумать и переработать целиком решение с учетом новых вводных.
Но!
Врага надо знать в лицо!
Поэтому в течение нескольких следующих постов мы будем обсуждать варианты инвазивного и неинвазивного доступа к приватным членам класса. Как говорится: не повторяйте в проде, чревато говнокодом по теории разбитых окон.
И на завтрак мы разберем самый простой способ. Макросы.
Есть у нас хэдэр:
// header.cpp
#pragma once
class X {
public:
X() : private_(1) { /.../
}
template <class T>
void f(const T &t) { /.../
}
int Value() { return private_; }
// ...
private:
int private_;
};
Подключаем его в цппшник, но перед этим делаем грязь:
#define private public
#include "source.h"
#include <iostream>
void Hijack( X& x )
{
x.private_ = 2;
}
int main() {
X x;
Hijack(x);
std::cout << "Hi, there! Hack has performed successfully" << std::endl;
}
Строчкой
#define private public вы заменяете все нижележащие по коду вхождения слова private на public. Таким образом вы не трогаете хэдэр, но все равно имеете доступ к абсолютно всем его полям и методам.И это работает! Да, стандартом запрещено заменять макросами ключевые слова. Но это вообще не волнует компиляторы. gcc даже c флагами -pedantic -Wall не выдает никаких предупреждений. clang только с флагом -pedantic генерирует варнинг.
Конечно же за такой макрос надо не то что по рукам бить. Надо их из жопы вырывать без анастезии и вставлять в нормальное место.
Пожалуй, это самый дурнопахнущих из всех способов, потому что ломает инкапсуляцию прям везде. Так сказать начали с вкуснятины. Но оставайтесь на свзяи, продолжение тоже будет вкусным.
Be legal. Stay cool.
#NONSTANDARD #badpractice
❤37😁12👍11🤯6😭6🔥5😱4🤣2
Доступ к приватным членам. Указатали
#новичкам
Если по-честному, то все эти спецификаторы доступа к членам класса, это чисто синтаксическое ограничение на непреднамеренное использование в коде имен непубличных членов. Ну и способ выделение в классе интерфейса, чтобы сказать другим программистам, какими легальными способами можно оперировать объектом.
Но если вы хотите непотребств, вас никто не может ограничить. С++ имеет прямой доступ к памяти, а значит вы можете посмотреть под лупой, понюхать и облизать любой байтик объекта.
То есть банально
Как бы логично предположить, что если у класса только одно поле, то сам объект будет состоять только из этого поля. И можно спокойно привести указатель на объект к указателю на поле.
Но здесь есть целых 2 проблемы.
1️⃣ Нарушение strict aliasing. Мы интерпретируем указатель на объект, как указатель на другой тип. Это UB по стандарту. Это значит, что ваше решение непереносимо и результат может отличаться в зависимости от компилятора и опций компиляции.
2️⃣ Вторая еще серьезнее. Цитата из стандарта:
Если ваша кодовая конструкция интерпретируется, как использование недоступных вам мемберов, то конструкция ill-formed. Не сказано, что сама программа ifndr, но это все равно значит, что код выше не соответствует правилам языка.
Первую проблему можно обойти с помощьюдыры в стандарте memcpy:
Но вторую проблему никак не убрать. Если вы получаете доступ к недоступным вам в текущем контексте полям через такие низкоуровневые инструменты, ваш код is dog shit.
Если мемберов много, то нужно будет учитывать выравнивание полей в объекте.
В общем, это все может работать на конкретной архитектуре и компиляторе, если вы сами все руками в каждом конкретном случае проверяете. Но стандарт вас осуждает и ничего не обещает.
Пусть в конце каждого поста из серии будет эпилог: получать доступ к приватным полям - плохо! Мы с вами это делаем для понимания механик языка, а не для вооружения здешних обителей оружием массового закакивания кода.
Be legal. Stay cool.
#cppcore #badpractice
#новичкам
Если по-честному, то все эти спецификаторы доступа к членам класса, это чисто синтаксическое ограничение на непреднамеренное использование в коде имен непубличных членов. Ну и способ выделение в классе интерфейса, чтобы сказать другим программистам, какими легальными способами можно оперировать объектом.
Но если вы хотите непотребств, вас никто не может ограничить. С++ имеет прямой доступ к памяти, а значит вы можете посмотреть под лупой, понюхать и облизать любой байтик объекта.
То есть банально
class MyClass {
private:
int secret = 42;
};
void illegalAccess(MyClass &obj) {
int *ptr = (int *)&obj; // assume that secret is first member
std::cout << "Illegal: " << *ptr << std::endl;
}Как бы логично предположить, что если у класса только одно поле, то сам объект будет состоять только из этого поля. И можно спокойно привести указатель на объект к указателю на поле.
Но здесь есть целых 2 проблемы.
1️⃣ Нарушение strict aliasing. Мы интерпретируем указатель на объект, как указатель на другой тип. Это UB по стандарту. Это значит, что ваше решение непереносимо и результат может отличаться в зависимости от компилятора и опций компиляции.
2️⃣ Вторая еще серьезнее. Цитата из стандарта:
The interpretation of a given construct is established without regard to access control. If the interpretation established makes use of inaccessible members or base classes, the construct is ill-formed. Если ваша кодовая конструкция интерпретируется, как использование недоступных вам мемберов, то конструкция ill-formed. Не сказано, что сама программа ifndr, но это все равно значит, что код выше не соответствует правилам языка.
Первую проблему можно обойти с помощью
void AccessWithMemcpy(MyClass& obj) {
int value;
std::memcpy(&value, &obj, sizeof(int));
std::cout << value << std::endl;
}Но вторую проблему никак не убрать. Если вы получаете доступ к недоступным вам в текущем контексте полям через такие низкоуровневые инструменты, ваш код is dog shit.
Если мемберов много, то нужно будет учитывать выравнивание полей в объекте.
В общем, это все может работать на конкретной архитектуре и компиляторе, если вы сами все руками в каждом конкретном случае проверяете. Но стандарт вас осуждает и ничего не обещает.
Пусть в конце каждого поста из серии будет эпилог: получать доступ к приватным полям - плохо! Мы с вами это делаем для понимания механик языка, а не для вооружения здешних обителей оружием массового закакивания кода.
Be legal. Stay cool.
#cppcore #badpractice
❤23👍17😁9🔥7❤🔥1😭1
Доступ к приватным членам. Интерпретация.
#опытным
Продолжаем исследовать грязь и разврат. Предыдущий пост тут.
Указатели, блин, сложная тема. А для задачи доступа к приватным полям - особенно. Для объектов классов с несколькими полями нужно считать много деталей: учитывать выравнивание полей в объекте и на этой базе высчитывать сдвиг типизированного указателя. Возможно даже несколько кастов понадобиться.
Программисты - люди ленивые, поэтому давайте как-то упрощать.
Допустим, у меня есть класс Target с множеством полей. А что, если где-то будет структурка Hack, идентичная по расположению полей? Может быть как-то можно интерпретировать данные Target, как данные Hack?
Конечно можно. В плюсах можно ВСЕ.
Конкретно интерпретировать одни данные, как другие, позволяет делать union. Он хранит один объект любого типа из списка, перечисленного внутри union. Но объединение не знает, объект какого конкретного типа он хранит. Это возлагается на плечи программиста, а union позволяет "достать" этот объект через любой тип.
Внутри union определяет структуру с точно таким же layout'ом, как у Target, только члены у нее публичные. И интерпретируем данные Target как набор полей этой структуры. Так мы можем достать stolen_x и stolen_y.
И это даже не UB, а вполне валидное поведение! Стандарт гарантирует, что обе структуры layout-compatible и имеют общую начальную последовательность полей. И в этом случае union может их читать без угроз со стороны библии С++.
Еcли же классы будут не такие простые, даже просто с полями с разными доступами, то стандарт уже гарантировать не будет. Как мы помним, в общем случае доступ к неактивному члену юниона - ub.
Кстати, если уж мы хотим "интерпретировать" один объект как другой, можно использовать приведение типов реинтерпретацией aka reinterpret_cast:
Тут уже без сомнения UB, потому что нарушаем strict aliasing, но это не мешает программе успешно отрабатывать.
Пс: Получать доступ к приватным полям вне класса - плохо! Мы с вами это делаем для понимания механик языка, а не для вооружения здешних обителей оружием массового закакивания кода.
#cppcore #badpractice
#опытным
Продолжаем исследовать грязь и разврат. Предыдущий пост тут.
Указатели, блин, сложная тема. А для задачи доступа к приватным полям - особенно. Для объектов классов с несколькими полями нужно считать много деталей: учитывать выравнивание полей в объекте и на этой базе высчитывать сдвиг типизированного указателя. Возможно даже несколько кастов понадобиться.
Программисты - люди ленивые, поэтому давайте как-то упрощать.
Допустим, у меня есть класс Target с множеством полей. А что, если где-то будет структурка Hack, идентичная по расположению полей? Может быть как-то можно интерпретировать данные Target, как данные Hack?
Конечно можно. В плюсах можно ВСЕ.
Конкретно интерпретировать одни данные, как другие, позволяет делать union. Он хранит один объект любого типа из списка, перечисленного внутри union. Но объединение не знает, объект какого конкретного типа он хранит. Это возлагается на плечи программиста, а union позволяет "достать" этот объект через любой тип.
struct Target {
private:
int x = 42;
double y = 3.14;
};
union HackUnion {
HackUnion() {}
Target target;
struct {
int stolen_x;
double stolen_y;
} hacker;
};
int main() {
HackUnion u;
u.target = Target();
std::cout << "Stolen x: " << u.hacker.stolen_x << std::endl;
std::cout << "Stolen y: " << u.hacker.stolen_y << std::endl;
}
// OUTPUT:
// Stolen x: 42
// Stolen y: 3.14Внутри union определяет структуру с точно таким же layout'ом, как у Target, только члены у нее публичные. И интерпретируем данные Target как набор полей этой структуры. Так мы можем достать stolen_x и stolen_y.
И это даже не UB, а вполне валидное поведение! Стандарт гарантирует, что обе структуры layout-compatible и имеют общую начальную последовательность полей. И в этом случае union может их читать без угроз со стороны библии С++.
Еcли же классы будут не такие простые, даже просто с полями с разными доступами, то стандарт уже гарантировать не будет. Как мы помним, в общем случае доступ к неактивному члену юниона - ub.
Кстати, если уж мы хотим "интерпретировать" один объект как другой, можно использовать приведение типов реинтерпретацией aka reinterpret_cast:
struct Target {
private:
int x = 42;
double y = 3.14;
};
struct Hacker {
int stolen_x;
double stolen_y;
};
Target target;
Hacker* hacker = reinterpret_cast<Hacker*>(&target);
std::cout << "Stolen x: " << hacker->stolen_x << std::endl;
std::cout << "Stolen y: " << hacker->stolen_y << std::endl;Тут уже без сомнения UB, потому что нарушаем strict aliasing, но это не мешает программе успешно отрабатывать.
Пс: Получать доступ к приватным полям вне класса - плохо! Мы с вами это делаем для понимания механик языка, а не для вооружения здешних обителей оружием массового закакивания кода.
#cppcore #badpractice
❤23👍15🔥6😁2