[C++] Kort fråga om constructors

Permalänk
Medlem

[C++] Kort fråga om constructors

God kväll!

(Har försökt googla till mig svaret men hittar inget vettigt (även på Stack Overflow) så vänder mig till er här!)

Jag har en enkel fråga om constructors (kör med C++11 just nu men kanske är samma för nyare/äldre versioner också). Antag att jag har en enkel header-fil samt en source-fil som ser ut så här:

// example.h #ifndef EXAMPLE_H #define EXAMPLE_H class Example { public: Example() {} // constructor void exampleFunction(uint32_t input); } #endif

// example.cpp #include <example.h> void Example::exampleFunction(uint32_t input) { std::cout << input << std::endl; }

Skapar jag ett objekt av Example i t.ex. en annan klass så kompilerar det utan problem. Om jag ändrar header-filen till följande så får jag dock ett kompileringsfel i klassen som hämtar objektet Example:

// example.h #ifndef EXAMPLE_H #define EXAMPLE_H class Example { public: Example(); // updated constructor; replaced curly brackets with semi-colon void exampleFunction(uint32_t input); } #endif

"undefined reference to Example::Example()"

Behåller jag den uppdaterade header-filen ovan men lägger till en "funktion" för constructorn i source-filen så kompilerar det dock utan problem:

// example.cpp #include <example.h> Example::Example() { } void Example::exampleFunction(uint32_t input) { std::cout << input << std::endl; }

Så min fråga är helt enkelt:

Varför måste constructor definieras med curly brackets i header-filen, om man väljer att inte skapa en definiton för constructorn i source-filen? Eller är det fel på min kompilator?

Tack för era svar!

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk
Medlem

Varför du måste ha "curly brackets" är för att kompilatorn måste koppla ihop definitionen med en implementation. Om jag inte missminner mig så kan du skippa att skapa definitionen av konstruktorn i header filen ifall du inte har någon användning för konstruktorn. Konstruktorn kommer då genereras/skapas automatiskt vid skapande av ett objekt.

Header fil med både definition och implementation:

class Example { public: Example() { // Implementation } }

Header fil för definition + .cpp fil för implementation:

class Example { public: Example(); }

#include <example.h> Example::Example() { // Implementation }

Permalänk
Medlem

@Icte:

Example();

är en forward-declaration - alltså en rad som beskriver hur funktionshuvudet ser ut (vilken indata som funktionen tar och vilken den returnerar).

Om du har en sådan, så förväntar sig kompilatorn att den kan hitta en definition av funktionen någonstans. (en fullständig implementation av funktionen).

Example::Example() { }

antingen har man hela implementationen i header-filen, eller så en forward-declaration i header-filen och en implementation av motsvarande funktion i c/cpp-filen.

Värt att tänka är då att om man har en implementation i en header-fil så KAN det bli besvär om den filen inkluderas från flera ställen.

Permalänk
Medlem
Skrivet av Warcaith:

Varför du måste ha "curly brackets" är för att kompilatorn måste koppla ihop definitionen med en implementation. Om jag inte missminner mig så kan du skippa att skapa definitionen av konstruktorn i header filen ifall du inte har någon användning för konstruktorn. Konstruktorn kommer då genereras/skapas automatiskt vid skapande av ett objekt.

Header fil med både definition och implementation:

class Example { public: Example() { // Implementation } }

Header fil för definition + .cpp fil för implementation:

class Example { public: Example(); }

#include <example.h> Example::Example() { // Implementation }

Skrivet av gusnan:

@Icte:

Example();

är en forward-declaration - alltså en rad som beskriver hur funktionshuvudet ser ut (vilken indata som funktionen tar och vilken den returnerar).

Om du har en sådan, så förväntar sig kompilatorn att den kan hitta en definition av funktionen någonstans. (en fullständig implementation av funktionen).

Example::Example() { }

antingen har man hela implementationen i header-filen, eller så en forward-declaration i header-filen och en implementation av motsvarande funktion i c/cpp-filen.

Värt att tänka är då att om man har en implementation i en header-fil så KAN det bli besvär om den filen inkluderas från flera ställen.

Stort tack för era svar, nu förstår jag! Detta har jag faktiskt aldrig riktigt tänkt på tidigare, så nu har jag lärt mig något nytt!

@gusnan: Angående att köra med implementationer direkt i header-filer - vad för typ av besvär skulle detta kunna orsaka om man inkluderar denna i olika klasser?

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk
Medlem

@Icte: Långa kompileringstider, om det är stora funktioner som implementeras. Eftersom funktionen inkluderas så kompileras den överallt där filen inkluderas med #include.

Om den implementeras i C-filen så kompileras den bara en gång.

Permalänk
Medlem
Skrivet av gusnan:

@Icte: Långa kompileringstider, om det är stora funktioner som implementeras. Eftersom funktionen inkluderas så kompileras den överallt där filen inkluderas med #include.

Om den implementeras i C-filen så kompileras den bara en gång.

Ahh du menar så. Ja bör ju stämma!

Stort tack för svar!

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk
Medlem

@Icte: Detta har alltså inget med just konstruktorer att göra, utan det är samma för alla funktioner att de måste vara definierade någonstans om de används i programmet. Det har att göra med att .cpp-filerna normalt kompileras separat, och sen länkas de ihop för att skapa det slutgiltiga programmet. Denna video från CppCon 2018 går igenom länkning och mycket annat av tvivelaktig nytta att kunna, men är väl värd att ses om man finner såna saker intressanta.

När det gäller konstruktorer så är dock konstruktorn i ditt exempel överflödig, eftersom den är identiskt med default-konstruktorn som skulle genereras automatiskt om man inte deklarerar någon konstruktor. Det går även att explicit tala om för kompilatorn att du vill att en default-konstruktor ska genereras genom att skriva:

class Example { public: Example() = default; };

Inte så användbart i just det här fallet, men kan användas i situationer när man vill definiera egna konstruktorer men även vill ha default-konstruktorn. = default går även att använda för alla andra typer av konstruktorer, destruktorer, samt tilldelningsoperatorer.

Man kan även tala om för kompilatorn att man inte vill att default-konstruktorn genereras, genom att istället använda delete:

code Example { public: Example() = delete; };

Permalänk
Medlem
Skrivet av perost:

@Icte: Detta har alltså inget med just konstruktorer att göra, utan det är samma för alla funktioner att de måste vara definierade någonstans om de används i programmet. Det har att göra med att .cpp-filerna normalt kompileras separat, och sen länkas de ihop för att skapa det slutgiltiga programmet. Denna video från CppCon 2018 går igenom länkning och mycket annat av tvivelaktig nytta att kunna, men är väl värd att ses om man finner såna saker intressanta.

När det gäller konstruktorer så är dock konstruktorn i ditt exempel överflödig, eftersom den är identiskt med default-konstruktorn som skulle genereras automatiskt om man inte deklarerar någon konstruktor. Det går även att explicit tala om för kompilatorn att du vill att en default-konstruktor ska genereras genom att skriva:

class Example { public: Example() = default; };

Inte så användbart i just det här fallet, men kan användas i situationer när man vill definiera egna konstruktorer men även vill ha default-konstruktorn. = default går även att använda för alla andra typer av konstruktorer, destruktorer, samt tilldelningsoperatorer.

Man kan även tala om för kompilatorn att man inte vill att default-konstruktorn genereras, genom att istället använda delete:

code Example { public: Example() = delete; };

Ja jag förstår nu att det även gäller funktioner i allmänhet och inte enbart konstruktorn! Mycket intressant video f.ö., länkning är något jag måste ta och se över lite närmare.

Nej precis, jag kom fram till exemplet ovan efter att ha tagit bort en initialisering av en variabel i konstruktorn och tyckte att curly brackets tog upp en rad för mycket. Normalt sett kör jag ju annars med default-konstruktorn i sådana fall (d.v.s. utan att deklarera en direkt i header-filen), men fick hjärnsläpp. Lite galet hur mycket man kan lära sig om C++ genom slumpmässiga misstag!

På tal om initialiseringar av konstruktorer, hittade denna intressanta artikel av en slump nu när jag googlade:

https://mikelui.io/2019/01/03/seriously-bonkers.html

Tl;dr
Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII