OOP
C++
OOP
Object-Oriented Programming
장점
- 캡슐화 encapsulation : 정보 은닉 information hiding
- 상속 inheritance
- 다형성 polymrphism
- 추상화 abstraction
캡슐화
접근 지정자
| 항목 | 간단 설명 | 내용 |
|---|---|---|
public |
공개 멤버 | 클래스 외부에서 자유롭게 접근 가능 |
protected |
보호된 멤버 | 클래스 외부에서 접근 불가능 파생 클래스(상속받은 클래스) 에서는 가능 |
private |
비공개 멤버 | 클래스 외부, 파생 클래스에서도 접근 불가 클래스 내부에서만 접근 가능 |
기타
연산자 오버로딩 strcat 대신 “aa” + “bb” = “aabb”
RAII : 스마트 포인터 : 객체의 라이프 사이클과 리소스 자동 관리, 메모루 누수 해결?
예외 처리
네임스페이스 : 이름 충돌 해결
RTTI 런타임 타입 정보
클래스
객체를 생성하기 위한 설계도 객체는 클래스의 인스턴스, 클래스에 정의된 속성과 동작을 실제로 구현
데이터(멤버 변수)와 동작(멤버 함수)를 하나로 묶는 사용자 정의 자료형
구조체와의 차이 : 기본 접근 수준 | | | | ————– | ——- | | 구조체(struct) | public | | 클래스(class) | private |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;
struct StructExample {
int x; // 기본적으로 public
};
class ClassExample {
int x; // 기본적으로 private
};
int main() {
StructExample s;
s.x = 10; // 접근 가능
// ClassExample c;
// c.x = 10; // 오류: private 멤버는 접근 불가능
return 0;
}
멤버 변수 & 멤버 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
using namespace::std;
class foo {
//멤버 변수
int a;
int b;
//멤버 함수
void func1() {
;
}
void func2() {
;
}
};
int main() {
}
static
static 정적 멤버 변수는 클래스의 모든 객체가 공유
일반 멤버 변수는 객체별 독립
멤버 함수는 공유
클래스 생성 위치
스택이 좋음 메모리 관리 알아서 해주기 때문
객체 생성 방법
-
스택 : 1순위 권장
Myclass obj; -
힙 1)
new,delete: 힙에 생성한다면 권장 : 생성자 호출이 가능해서?
Myclass* obj = new Myclass();2)malloc,free
free 이후에
생성자 주소에 nullptr 넣어서 해제된 객체 사용 막기
초기화
1
2
int x = 0; //암묵적 형변환 -> 복사 초기화
int x{0}; //명시적 형변환 -> 타입에 맞지 않을 경우 컴파일 에러 오류 -> 더 안전
this
현재 객체를 가리키는 포인터
자기 자신의 주소
인스턴스 메서드 안에서만 사용 가능
namespace
이름 공간 정의
이름 충돌 방지
::가 C++에서 우선순위 가장 높음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
namespace our {
void init(){
printf("our");
}
}
namespace your {
void init() {
printf("your");
}
}
int main() {
our::init();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
using namespace std;
namespace A {
void init() {
cout << "A namespace" << endl;
}
}
namespace B {
void init() {
cout << "B namespace" << endl;
}
}
void init() {
cout << "global namespace" << endl;
}
int main() {
A::init();
B::init();
init();
}
using namespace
하나의 함수명만 사용할 경우
그냥 함수명으로 호출 가능
-> 충돌 가능성 있음
cin, cout
1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
using std::cout;
using std::cin;
int main() {
int a;
cin >> a;
cout << a;
}
using, typedef
C++ 가급적 using 사용, typedef 대신
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using namespace std;
typedef int i;
using ii = int;
int main() {
i a = 1;
ii b = 2;
cout << a << endl;
cout << b << endl;
}
auto
복잡한 형태 auto로 단순하게 선언
decletype
표현식의 타입을 추론
변수나 함수 반환 값의 타입을 정확하게 알 수 없을 때 유용
1
2
3
4
5
6
7
8
9
#include <iostream>
int main() {
int x = 5;
decltype(x) y = 2; // 'x'와 동일한 타입인 'int'로 선언
std::cout << y << std::endl; // 출력: 2
return 0;
}
균일한 초기화 (uniform initialization)
중괄호 {}를 사용하여 모든 타입의 변수 초기화 가능
축소 변환(narrowing conversion) 방지 -> 데이터 손실 발생할 수 있는 초기화 허용 x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>
class Person {
public:
std::string name;
int age;
Person(std::string n, int a) : name{n}, age{a} {} // 균일한 초기화 사용
};
int main() {
int a{10}; // 정수 초기화
double b{3.14}; // 실수 초기화
char c{'A'}; // 문자 초기화
std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
Person p{"Alice", 25}; // 객체 초기화
std::cout << "Name: " << p.name << ", Age: " << p.age << std::endl;
return 0;
}
string 관한
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <string>
// include <csting.h> string.h의 cpp 버전
int main(void) {
char s1[5] = "abcd";
char s2[5] = { 0 };
// s1을 s2로 복사
strcpy_s(s2, s1);
std::cout << s1 << std::endl;
std::cout << s2 << std::endl;
// 두문자열 같은지
if (strcmp(s2, s1) == 0) { std::cout << "same" << std::endl; }
else { std::cout << "not same" << std::endl; }
std::string s3 = "xyzw";
std::string s4 = "";
// = 연산자
s4 = s3;
std::cout << s3 << std::endl;
std::cout << s4 << std::endl;
// == 연산자
if (s3 == s4) { std::cout << "same" << std::endl; }
else { std::cout << "not same" << std::endl; }
// + 연산자
std::string s5 = s3 + s4;
std::cout << s5 << std::endl;
return (0);
}
오버로딩 Overloading
같은 이름 함수 여러 개 정의
매개변수가 다르면 같은 이름으로 여러 함수 정의
-> 컴파일 시점에 결정, 변환 타입은 오버로딩x
오버라이딩 Overriding
부모 클래스에서 정의돈 함수를 자식 클래스에서 재정의
상속 관게에서 덮어쓰기
함수 이름, 매개변수, 반환 타입이 동일해야 함
-> 런타임 시점에결정정
| 구분 | 오버로딩 | 오버라이딩 |
|---|---|---|
| 적용 대상 | 같은 클래스 내에서 사용 | 부모 클래스와 자식 클래스 간 사용 |
| 관계 | 상속 관계가 필요 없음 | 상속 관계 필수 |
| 재정의 조건 | 함수 이름 같고 매개변수 타입/개수/순서가 달라야 함 | 함수 이름, 매개변수, 반환 타입이 모두 같아야 함 |
| 바인딩 시점 | 컴파일 시점 (정적 바인딩) | 런타임 시점 (동적 바인딩, 가상 함수 사용) |
| 키워드 필요 여부 | 별도의 키워드 필요 없음 | virtual과 override 키워드 사용 권장 |
| 예제 용도 | 여러 매개변수 타입/개수에 대응하기 위해 사용 | 부모 클래스의 동작을 자식 클래스에서 재정의하여 변경 |
예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() {
cout << "Animal makes a sound." << endl;
}
};
class Dog : public Animal {
public:
void sound() override {
cout << "Dog barks." << endl;
}
void callParentSound() {
Animal::sound(); // 부모 클래스의 sound() 호출
}
};
int main() {
Dog dog;
dog.sound(); // 자식 클래스의 sound() 호출
dog.callParentSound(); // 부모 클래스의 sound() 호출
return 0;
}
네임 맹글링
C++ 함수 오버로딩 위해서
같은 이름의 함수나 변수를 링커 단계에서 고유하게 식별할 수 있도록 이름을 변환
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;
void foo(int x) {
cout << "foo(int)" << endl;
}
void foo(double x) {
cout << "foo(double)" << endl;
}
int main() {
foo(10); // 호출: foo(int)
foo(3.14); // 호출: foo(double)
return 0;
}
컴파일 후 변환된 이름
foo(int) -> _Z3fooi
foo(double) -> _Z3food
함수 템플릿
teplate <typename T>
타입 매개변수 T는 호출 시점에 컴파일러가 전달된 타입에 따라 자동으로 대체
template <class T> 도 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
// 함수 템플릿 정의
template <typename T>
T square(T x) {
return x * x;
}
int main() {
// 템플릿 인수 명시적으로 지정
double d = square<double>(3.2); // T를 double로 명시
cout << "결과: " << d << endl; // 출력: 10.24
// 템플릿 인수를 생략 (컴파일러가 자동 추론)
int i = square(5); // T가 int로 자동 추론
cout << "결과: " << i << endl; // 출력: 25
return 0;
}
다형성
OCP (Open/Closed Principle) 개방 폐쇄 원칙
확장에는 열려있고, 변경에는 닫혀있도록
-> C는 콜백(함수 포인터)
-> CPP(OOP)는 인터페이스
부모 클래스의 virtual 키워드를 자식 클래스에서 오버라이딩 해서 사용
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream>
using namespace std;
class Base {
public:
virtual ~Base() { //가상 소멸자
std::cout << "Base destructor called" << std::endl;
}
virtual void show() { // 가상 함수
std::cout << "Base class show() called" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() { //가상 소멸자
std::cout << "Derived destructor called" << std::endl;
}
void show() override { // 자식 클래스에서 재정의된 메서드
std::cout << "Derived class show() called" << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
basePtr->show(); // 동적 바인딩, Derived 클래스의 show() 호출
delete basePtr;
return 0;
}
Derived class show() called
Derived destructor called
Base destructor called
인터페이스
특정 클래스를 정의하면서 그 클래스가 제공해야 하는 메서드를 선언하는 추상적인 개념
순수 가상 함수(Pure Virtual Function)을 사용하여 구현 -> override 강제
virtual키워드와 함께 = 0으로 정의
1
virtual void draw() const = 0; // const : 읽기 전용
추상 클래스(abstract class) : 순수 가상 함수를 포함한 클래스
객체로 만들지 못하고 상속으로써만 사용
상속받은 자식 클래스는 순수 가상 함수를 오버라이딩(override) 해야함
1
2
3
4
5
6
7
// 자식 클래스가 인터페이스를 구현
class Rectangle : public IShape {
public:
void draw() const override { // 인터페이스의 순수 가상 함수를 재정의
std::cout << "Drawing Rectangle" << std::endl;
}
};
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
using namespace std;
// 추상 클래스
class Shape {
public:
virtual void draw() const = 0; // 순수 가상 함수
virtual ~Shape() {}
};
// 구체적인 형태의 클래스
class Circle : public Shape {
public:
void draw() const override {
cout << "Drawing a Circle" << endl;
}
};
class Rectangle : public Shape {
public:
void draw() const override {
cout << "Drawing a Rectangle" << endl;
}
};
// 새로운 형태를 추가할 때 코드 변경 없이 확장 가능
class Triangle : public Shape {
public:
void draw() const override {
cout << "Drawing a Triangle" << endl;
}
};
int main() {
Shape* shape;
// 기존 코드 변경 없이 새로운 형태 추가
shape = new Circle();
shape->draw();
delete shape;
shape = new Rectangle();
shape->draw();
delete shape;
shape = new Triangle();
shape->draw();
delete shape;
return 0;
}
상속
생성 & 소멸
하위 객체 생성시 -> 상위 객체 먼저 생성
하위 객체 소멸시 -> 하위 객체 먼저 소멸
구성 (composition)
클래스가 다른 클래스를 멤버 변수로 포함하는 방식으로 기능을 확장
상속 x, 직접 객체를 포함
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
class Engine {
public:
void start() const {
std::cout << "Engine starts" << std::endl;
}
};
class Car {
private:
Engine engine; // Engine 객체를 멤버 변수로 포함
public:
void start() const {
engine.start(); // Engine 객체의 메서드 호출
}
};
int main() {
Car myCar;
myCar.start(); // Car가 Engine을 구성하여 메서드를 호출
return 0;
}
멤버 함수 대체 (Member Function Delegation)
부모 클래스에서 제공하는 특정 기능을 자식 클래스에서 사용하기 위해 부모 클래스의 멤버 함수를 호출하는 방식
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
class Shape {
public:
virtual void draw() const {
std::cout << "Drawing Shape" << std::endl;
}
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing Circle" << std::endl;
}
void drawBorder() const {
Shape::draw(); // 부모 클래스의 draw() 메서드를 호출
std::cout << "Drawing Circle Border" << std::endl;
}
};
int main() {
Circle circle;
circle.draw(); // 자식 클래스가 재정의한 draw()
circle.drawBorder(); // 자식 클래스가 부모의 draw()를 대체 호출
return 0;
}
| 구성(Composition) | 멤버 함수 대체(Member Function Delegation) |
|---|---|
| 다른 클래스를 멤버 변수로 포함하여 기능을 추가 | 부모 클래스의 메서드를 자식 클래스에서 호출하여 기능 확장 |
| 코드의 결합도를 낮추고 유연성 증가 | 부모 클래스의 메서드를 사용하여 새로운 동작 추가 |
| 클래스 간 독립적인 객체 관계 유지 | 자식 클래스가 부모 클래스의 기능을 호출하여 확장 |
생성자
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;
// 클래스 정의
class Car {
private:
string brand;
int year;
public:
// 생성자 정의
Car(string b, int y) {
brand = b;
year = y;
}
void display() {
cout << "Brand: " << brand << ", Year: " << year << endl;
}
};
int main() {
Car car1("Toyota", 2020);
car1.display(); // Output: Brand: Toyota, Year: 2020
return 0;
}
부모랑 자식 객체 간 멤버 변수 다르면 부모 클래스에 기본 생성자 필요
-> 빈 멤버 변수가 생겨서 별로 안좋음
-> 구성을 통해
소멸자
업 캐스팅 (Up casting)
상위 객체 자리에 하위 객체가 대신
1
2
다운 캐스팅 (Down casting)
하위 객체 자리에 상위 객체가 대신
1
2
예제 : 다형성 : 개선 전, 후 비교
개선 전 코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
class LandLinePhone {
public:
void landlineCall() const {
std::cout << "유선 전화를 걸다." << std::endl;
}
};
class CellPhone {
public:
void cellPhoneCall() const {
std::cout << "무선 전화를 걸다." << std::endl;
}
};
class SmartPhone {
public:
void smartPhoneCall() const {
std::cout << "영상 전화를 걸다." << std::endl;
}
};
class Person {
public:
void makeCall(const LandLinePhone* phone) const {
phone->landlineCall();
}
void makeCall(const CellPhone* phone) const {
phone->cellPhoneCall();
}
void makeCall(const SmartPhone* phone) const {
phone->smartPhoneCall();
}
};
int main() {
Person person;
LandLinePhone landLinePhone;
person.makeCall(&landLinePhone);
CellPhone cellPhone;
person.makeCall(&cellPhone);
SmartPhone smartPhone;
person.makeCall(&smartPhone);
return 0;
}
개선 후 코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
class Phone {
public:
virtual void Call() const = 0;
};
class LandLinePhone : public Phone {
public:
void Call() const override {
std::cout << "유선 전화를 걸다." << std::endl;
}
};
class CellPhone : public Phone {
public:
void Call() const override {
std::cout << "무선 전화를 걸다." << std::endl;
}
};
class SmartPhone : public Phone {
public:
void Call() const override {
std::cout << "영상 전화를 걸다." << std::endl;
}
};
class Person {
public:
void makeCall(const Phone* phone) const {
phone->Call();
}
};
int main() {
Person person;
LandLinePhone landLinePhone;
person.makeCall(&landLinePhone);
CellPhone cellPhone;
person.makeCall(&cellPhone);
SmartPhone smartPhone;
person.makeCall(&smartPhone);
return 0;
}
연산자 오버로딩
연산자 오버로딩이 되지 않는 연산자도 있음
연산자 오버로딩 : 멤버함수로
기본 코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
class Point {
private:
int x;
int y;
public:
Point() : x{ 0 }, y{ 0 } {} // 기본 생성자
Point(int x, int y) : x{ x }, y{ y } {// 매개 변수 생성자
}
void print() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};
int main(void) {
Point pt1 = { 11,22 };
Point pt2 = { 22,33 };
pt1.print();
pt2.print();
return 0;
}
그냥 연산자 오버로딩 시도 : 에러
1
2
3
4
5
6
7
8
9
10
11
12
13
int main(void) {
Point pt1 = { 11,22 };
Point pt2 = { 22,33 };
pt1.print();
pt2.print();
Point pt3 = pt1 + pt2; // 에러
pt2.print();
return 0;
}
올바른 연산자 오버로딩 추가
1
2
3
4
5
6
7
8
9
class Point {
...
//연산자 오버로딩
Point operator+(const Point& other) const {
return Point(this->x + other.x, this->y + other.y);
}
}
최종 코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
class Point {
private:
double x;
double y;
public:
//기본 생성자 : 객체를 초기화시 인자가 없는 경우 호출
Point() : x{ 0 }, y{ 0 } {}
//매개 변수 생성자 : 객체 초기화시 인자 x,y로 초기화
Point(double x, double y) : x{ x }, y{ y } {
}
void print() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
Point operator+(const Point& other) const {
return Point(this->x + other.x, this->y + other.y);
}
Point operator-(const Point& other) const {
return Point(this->x - other.x, this->y - other.y);
}
Point operator*(const Point& other) const {
return Point(this->x * other.x, this->y * other.y);
}
Point operator/(const Point& other) const {
return Point(this->x / other.x, this->y / other.y);
}
};
int main(void) {
Point pt1 = { 11,22 };
Point pt2 = { 22,33 };
pt1.print();
pt2.print();
Point pt3;
pt3 = pt1 + pt2;
pt3.print();
pt3 = pt1 - pt2;
pt3.print();
pt3 = pt1 * pt2;
pt3.print();
pt3 = pt1 / pt2;
pt3.print();
return 0;
}
연산자 오버로딩 : 비멤버함수로
변경 방법 :
멤버변수 참조 위해 private -> public
const Point& other -> const Point& a, const Point& b
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
class Point {
public:
double x;
double y;
public:
//기본 생성자 : 객체를 초기화시 인자가 없는 경우 호출
Point() : x{ 0 }, y{ 0 } {}
//매개 변수 생성자 : 객체 초기화시 인자 x,y로 초기화
Point(double x, double y) : x{ x }, y{ y } {
}
void print() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};
Point operator+(const Point& a, const Point& b) {
return Point(a.x + b.x, a.y + b.y);
}
Point operator-(const Point& a, const Point& b) {
return Point(a.x - b.x, a.y - b.y);
}
Point operator*(const Point& a, const Point& b) {
return Point(a.x * b.x, a.y * b.y);
}
Point operator/(const Point& a, const Point& b) {
return Point(a.x / b.x, a.y / b.y);
}
int main(void) {
Point pt1 = { 11,22 };
Point pt2 = { 22,33 };
pt1.print();
pt2.print();
Point pt3;
pt3 = pt1 + pt2;
pt3.print();
pt3 = pt1 - pt2;
pt3.print();
pt3 = pt1 * pt2;
pt3.print();
pt3 = pt1 / pt2;
pt3.print();
return 0;
}
예제 : 연산자 오버로딩 + 로 strcat 만들기
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include<cstring>
class Mystring {
private:
char* str;
int len;
public:
//기본 생성자 : 빈 문자열로 초기화
Mystring() : str{ nullptr }, len{ 0 } {}
//매개 변수 생성자 : 입력 문자열로 초기화
Mystring(const char* inputstr) {
if (inputstr) {
len = std::strlen(inputstr);
str = new char[len + 1];
std::strcpy(str, inputstr);
}
else {
str = nullptr;
len = 0;
}
}
//복사 생성자
Mystring(const Mystring& other) {
len = other.len;
if (other.str) {
str = new char[len + 1];
std::strcpy(str, other.str);
}
else {
str = nullptr;
}
}
//소멸자 : 동적 메모리 해제
~Mystring() {
std::cout << str << "소멸" << std::endl;
delete[] str;
//delete[] :
//배열의 각 요소를 개별적으로 소멸 처리
//배열에 할당된 메모리 해제
}
//+연산자 오버로딩 : 문자열 합치기
Mystring operator+(const Mystring& other) const {
int newlen = len + other.len;
char* newstr = new char[newlen + 1];
if (str)std::strcpy(newstr, str); //복사하기
if (other.str)std::strcat(newstr, other.str); //뒤에 붙이기
return Mystring(newstr);
}
void print() const {
if (str) {
std::cout << str << std::endl;
}
else {
std::cout << "Empty" << std::endl;
}
}
};
int main(void) {
Mystring str1("hi");
Mystring str2(", nice to meet you");
Mystring str3 = str1 + str2;
str1.print();
str2.print();
str3.print();
std::cout << std::endl;
return 0;
}