Přeskočit na obsah

Funkce a předávání parametrů

Zvládněte různé způsoby předávání argumentů v C++, pochopte optimalizaci návratové hodnoty, přetěžování funkcí a moderní atributy jako [[nodiscard]].

Funkce: Základní abstrakce

Funkce jsou základní jednotkou organizace kódu v C++. Na rozdíl od mnoha jiných jazyků vám C++ poskytuje jemnou kontrolu nad tím, jak jsou argumenty předávány a vraceny z funkcí. Tato kontrola není jen syntaktický cukr – má skutečné důsledky pro výkon.

Způsob předávání parametrů určuje, zda funkce pracuje s kopií argumentu, odkazem na originál nebo ukazatelem na originál. Výběr správného mechanismu je jedním z nejdůležitějších rozhodnutí, která učiníte při navrhování rozhraní funkce.

Mechanismy předávání parametrů

C++ podporuje čtyři primární způsoby předávání argumentů. Každý z nich má různé kompromisy ohledně výkonu, bezpečnosti a sémantiky:

parameter_passing.cppc++23
#include <iostream>
#include <string>
#include <vector>

// 1. Pass by VALUE — the function gets a copy
//    Use for: small types (int, double, char), when you need a local copy
void square(int x) {           // x is a copy of the argument
    x = x * x;                 // Modifying x does NOT affect the caller
    std::cout << "Inside: " << x << '\n';
}

// 2. Pass by REFERENCE — the function works on the original
//    Use for: when you need to modify the caller's variable
void double_it(int& x) {       // x IS the caller's variable
    x *= 2;                    // This DOES modify the caller's variable
}

// 3. Pass by CONST REFERENCE — read-only access to the original, no copy
//    Use for: large objects you only need to read
void print_all(const std::vector<std::string>& names) {
    for (const auto& name : names) {
        std::cout << name << '\n';
    }
    // names.push_back("Eve");  // ERROR: names is const
}

// 4. Pass by POINTER — explicit nullable indirection
//    Use for: optional parameters (can be nullptr), C API interop
void maybe_increment(int* ptr) {
    if (ptr != nullptr) {       // Must check for null!
        ++(*ptr);
    }
}

int main() {
    int val = 5;

    square(val);
    std::cout << "After square: " << val << '\n';      // Still 5 (copied)

    double_it(val);
    std::cout << "After double_it: " << val << '\n';   // Now 10 (modified)

    std::vector<std::string> people = {"Alice", "Bob", "Charlie"};
    print_all(people);         // No copy of the vector — efficient

    maybe_increment(&val);     // Pass address explicitly
    std::cout << "After maybe_increment: " << val << '\n';  // 11

    maybe_increment(nullptr);  // Safe: the function checks for null
}
Výstup

Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.

Osvědčený postup

Při výběru způsobu předávání parametrů se řiďte těmito pokyny ze základních pokynů C++:

- Kopírování je levné a nikdy neupravováno? Předávání hodnoty (int,double,char,bool, malé struktury,std::string_view).
- Nákladné na kopírování a pouze čtení? Projděte kolemconst&(std::string,std::vector, velké objekty).
- Potřebujete upravit proměnnou volajícího? Projděte&(non-const reference).
- Může být null? Projeďte ukazatelem. Ale místo toho zvažtestd::optional&.
- Převzetí vlastnictví (potopení)? Projděte hodnotou a pohybujte se (zahrnuto v modulu sémantiky přesunu).

Obecně platí, že objekty větší než 2-3 ukazatele (16-24 bajtů na 64bitové verzi) by měly být předány odkazem, nikoli hodnotou.

  • Předávejte malé levné typy podle hodnoty: int, double, char, bool, string_view
  • Předat velké objekty pouze pro čtení pomocí odkazu const: const std::string&, const std::vector&
  • Pass by non-const reference only when the function needs to modify the caller's object
  • Předejte ukazatel, když je nullptr platným argumentem — ale pokud je to možné, upřednostňujte std::optional
  • When in doubt, pass by const reference — it is safe and efficient

Return Value Optimization (RVO/NRVO)

Začátečníci se často obávají, že vracení velkých objektů podle hodnoty je drahé kvůli kopírování. V moderním C++ je tato obava téměř vždy neopodstatněná díky Optimalizaci návratnosti (RVO) a Pojmenované optimalizaci návratnosti (NRVO).

RVO (zaručeno od C++17): Když funkce vrátí dočasné (např.return std::vector{1,2,3};), kompilátor musí vytvořit objekt přímo v paměti volajícího. Nedochází ke kopírování ani přesunutí.

NRVO (povoleno, nezaručeno): Když funkce vrátí pojmenovanou lokální proměnnou (např.return result;), kompilátor může tuto proměnnou zkonstruovat přímo v paměti volajícího. Všechny hlavní kompilátory to v praxi dělají, dokonce i v režimu ladění.

Praktická rada: volná návratnost hodnoty. Nepoužívejte výstupní parametry (void f(std::vector& out)), abyste se vyhnuli kopírování – nechte kompilátor optimalizovat návratnost.

Function Overloading & Default Arguments

C++ allows multiple functions with the same name but different parameter lists. The compiler selects the best match based on the arguments provided at the call site:

overloading.cppc++23
#include <iostream>
#include <string>
#include <vector>

// Function overloading — same name, different parameter types/counts
void log(int value) {
    std::cout << "[INT] " << value << '\n';
}

void log(double value) {
    std::cout << "[DOUBLE] " << value << '\n';
}

void log(const std::string& value) {
    std::cout << "[STRING] " << value << '\n';
}

// Default arguments — provide fallback values
void greet(const std::string& name, const std::string& greeting = "Hello") {
    std::cout << greeting << ", " << name << "!\n";
}

// [[nodiscard]] (C++17) — warn if return value is ignored
[[nodiscard]] int compute(int a, int b) {
    return a * b + a;
}

// [[nodiscard]] with reason (C++20)
[[nodiscard("Ignoring error codes can hide failures")]]
int open_file(const std::string& path) {
    // Returns 0 on success, negative on error
    return 0;  // Simplified
}

// inline — suggests the compiler inline this function (also allows
// the function to be defined in a header without ODR violations)
inline int fast_max(int a, int b) {
    return (a > b) ? a : b;
}

int main() {
    log(42);               // Calls log(int)
    log(3.14);             // Calls log(double)
    log(std::string{"hi"});// Calls log(const string&)

    greet("Alice");                // Uses default: "Hello, Alice!"
    greet("Bob", "Good morning"); // Uses provided: "Good morning, Bob!"

    // int result = compute(3, 4);  // OK
    // compute(3, 4);               // WARNING: ignoring [[nodiscard]] return value

    int status = open_file("data.txt");  // OK: return value used
}
Výstup

Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.

Úskalí

Řešení přetížení funkcí se řídí složitými pravidly a nejednoznačná volání jsou běžným zdrojem chyb při kompilaci. Buďte si vědomi těchto úskalí:

1. Implicitní konverze vytváří nejednoznačnost: Pokud mátef(int)af(double), voláníf(3.14f)(float) je nejednoznačné, protožefloatpřevádí stejně dobře naintidouble. Oprava: přidejte přetíženíf(float)nebo použijtestatic_cast.

2. Výchozí argumenty špatně interagují s přetížením:void f(int x, int y = 0)avoid f(int x)vytvářejí nejednoznačnost, když jsou volány s jedním argumentem.

3. konst reference vs hodnota:f(int)af(const int&)jsou nejednoznačné pro argumenty lvalue, protože oba jsou stejně dobré.

Když kompilátor hlásí nejednoznačné přetížení, nehádejte – pozorně si přečtěte chybovou zprávu. Řekne vám, která přetížení jsou kandidáty a proč jsou stejně dobrá.

  • Implicit conversions can make overload resolution ambiguous — add explicit overloads to resolve
  • Default arguments and overloads can conflict — avoid both on the same function name
  • When in doubt, the compiler error message lists the candidate overloads and the ambiguity
  • Use static_cast to explicitly select an overload when calling with arguments that match multiple overloads
Klíčové poznatky
  • Předávat malé typy podle hodnoty, velké typy pouze pro čtení podle const reference a proměnlivé typy podle non-const reference
  • Return by value freely — RVO/NRVO eliminates the copy in practice
  • Přetížení funkcí vybere nejlepší shodu na základě typů argumentů — dejte si pozor na nejednoznačnosti z implicitních převodů
  • Použijte [[nodiscard]] na funkce, jejichž návratová hodnota nesmí být ignorována (chybové kódy, vypočítané výsledky)
  • Výchozí argumenty poskytují pohodlí, ale mohou být v konfliktu s přetížením – použijte jeden nebo druhý
  • inline umožňuje definovat funkce v hlavičkách, aniž by došlo k porušení pravidla jedné definice

Kvíz — Otestujte své znalosti

(15 XP)

1. Kdy byste měli předat`std::vector<int>`podle`const&`místo podle hodnoty?

2. Co je optimalizace návratnosti (RVO)?

3. Co dělá atribut`[[nodiscard]]`?