Tomek dostał 3 jabłka, ale 2 zjadł. Ile jabłek ma Tomek?
Nie wiadomo, bo nie wiemy ile miał wcześniej zanim dostał 3 jabłka.
Zawsze inicjalizuj zmienne.
int a;
a += 3;
a -= 2;
std::cout << a << '\n';
int index = 0; // ok
bool completed; // bad
bool finished = false; // ok
if (completed) { // very bad
// ...
}
struct Node {
Node* next = nullptr; // ok
int value = 0;
}
Kod pełny nazw domenowych (własnych typów) lepiej się czyta. Także osoby nie zajmujące się programowaniem na co dzień potrafią dużo na jego podstawie wydedukować.
Zobacz te 2 funkcje:
std::vector<std::pair<uint8_t, uint8_t>> compressGrayscale(std::array<std::array<uint8_t, width>, height>&);
std::array<std::array<uint8_t, width>, height> decompressGrayscale(std::vector<std::pair<uint8_t, uint8_t>>&);
I zobacz je teraz:
CompressedImage compressGrayscale(const Image& bitmap);
Image decompressGrayscale(const CompressedImage& compression);
Aby to osiągnąć wystarczy nadać inne nazwy typom w taki sposób:
using Image = std::array<std::array<uint8_t, width>, height>;
using CompressedImage = std::vector<std::pair<uint8_t, uint8_t>>;
Używaj deklaracji zapowiadających tam gdzie to możliwe zamiast #include
.
Kompilator, aby używać jakiegoś obiektu potrzebuje informacji o jego rozmiarze.
#include "Fruit.hpp"
int main() {
Fruit apple;
// ...
}
Aby można było utworzyć zmienną lokalną potrzebuje więc znać jej rozmiar. Musi więc mieć odpowiedni nagłówek, w którym ta klasa jest zdefiniowana, aby znać rozmiar takiej zmiennej na stosie.
-
Jaki rozmiar ma wskaźnik na
int
? -
Jaki rozmiar ma wskaźnik na
Fruit
?
Rozmiar wskaźnika nie zależy od typu na który wskazuje. Zazwyczaj ma on 4 lub 8 bajtów (odpowiednio dla procesorów 32 i 64 bitowych).
Każdy wskaźnik oraz referencja mają ten sam rozmiar. Dopóki się nimi posługujemy i nie odwołujemy się do żadnych metod lub pól klasy, to informacja o jej rozmiarze lub zawartości nie jest nam potrzebna.
class Fruit; // class forward declaration
void pass(Fruit* fruit) {
if (!fruit) {
return;
}
// do sth;
}
W takim przypadku wystarczy, że powiemy kompilatorowi, że typ wskaźnika jest naszą klasą za pomocą deklaracji zapowiadającej (forward declaration). Nie trzeba wtedy stosować #include
i przyspieszamy dzięki temu kompilację.
Jeśli jednak odwołamy się do jakiegoś pola lub metody tego obiektu to kompilator powie nam, że ma niepełne informacje.
class Fruit;
unsigned getFruitPrice(Fruit* fruit) {
if (!fruit) {
return 0u;
}
return fruit->getPrice(); // compilation error
}
W takim przypadku potrzeba dołączyć właściwy nagłówek.
class Apple {
int weight_;
public:
Apple(int weight); // possible conversion from int to Apple
};
void takeApple(Apple a);
int main() {
takeApple(150); // int converted implicitly into Apple
}
Słowo kluczowe explicit
zabrania niejawnych konwersji. Stosuje się je przy konstruktorach, które mogą przypadkiem spowodować konwersję z typu argumentu który przyjmują.
class Apple {
int weight_;
public:
explicit Apple(int weight);
};
void takeApple(Apple a);
int main() {
takeApple(150); // compilation error, expected Apple, got int
}
Słowo kluczowe explicit
stosujemy przy:
- konstruktorach jednoargumentowych
explicit Apple(int weight);
- konstruktorach wieloargumentowych, z jednym nie-domyślnym argumentem
explicit Apple(int weight, int size = 1);
- operatorach konwersji
explicit operator int() const { return weight_; }
Czytanie i stosowanie CppCoreGuidelines
- F.1: "Package" meaningful operations as carefully named functions
- F.2: A function should perform a single logical operation
- F.3: Keep functions short and simple
- F.4: If a function may have to be evaluated at compile time, declare it constexpr
- F.5: If a function is very small and time-critical, declare it inline
- F.6: If your function may not throw, declare it noexcept
- F.9: Unused parameters should be unnamed
- F.16: For "in" parameters, pass cheaply-copied types by value and others by reference to const
- F.17: For "in-out" parameters, pass by reference to non-const
- F.20: For "out" output values, prefer return values to output parameters
- F.21: To return multiple "out" values, prefer returning a struct or tuple
- F.60: Prefer T* over T& when "no argument" is a valid option
- F.26: Use a unique_ptr to transfer ownership where a pointer is needed
- F.27: Use a shared_ptr to share ownership
- C.1: Organize related data into structures (structs or classes)
- C.2: Use class if the class has an invariant; use struct if the data members can vary independently
- C.7: Don't define a class or enum and declare a variable of its type in the same statement
- C.8: Use class rather than struct if any member is non-public
- C.9: Minimize exposure of members
- Enum.1: Prefer enumerations over macros
- Enum.2: Use enumerations to represent sets of related named constants
- Enum.3: Prefer enum classes over "plain" enums
- Enum.4: Define operations on enumerations for safe and simple use
- Enum.5: Don't use ALL_CAPS for enumerators
- Enum.6: Avoid unnamed enumerations
- Enum.7: Specify the underlying type of an enumeration only when necessary
- Enum.8: Specify enumerator values only when necessary