C++과 C#에서 Struct와 Class가 무슨 차이가 있는지
그리고 class를 struct로 변경할 경우 어떤 문제가 발생 할 수 있는지 알아보도록 한다.
C++의 Struct와 Class의 차이
C++에서 struct와 class는 비슷하지만 기본 접근 지정자와 상속의 차이가 있다.
기본 접근 지정자 | 기본 상속 지정자 | |
Struct | public | public |
Class | private | private |
아래와 같이 구조체와 클래스를 만들었을 때 둘다 명시적으로 접근 지정자를 써주지 않는다면
struct는 open-minded이고 class는 shy-guy다.
#include<iostream>
using namespace std;
struct MyStruct {
string parent = "Struct!";
void WhoIAm() {
cout << "I am " << parent << '\n';
}
};
class MyClass {
string parent = "Class!";
void WhoIAm() {
cout << "I am " << parent << '\n';
}
};
int main() {
MyStruct myStruct;
MyClass myClass;
myStruct.WhoIAm();
myClass.WhoIAm(); <<== 오류
return 0;
}
메인 함수에서 class의 함수에 접근할 수 없다.
상속을 받을 때도 마찬가지이다.
#include<iostream>
using namespace std;
struct MyStruct {
string parent = "Struct!";
void WhoIAm() {
cout << "I am " << parent << '\n';
}
};
struct ChildStruct : MyStruct {
void MyParentIs() {
cout << parent << " is my parent \n";
}
};
class MyClass {
public:
string parent = "Class!";
void WhoIAm() {
cout << "I am " << parent << '\n';
}
};
class ChildClass : MyClass {
public:
void MyParentIs() {
cout << parent << " is my parent \n";
}
};
int main()
{
ChildStruct childStruct;
ChildClass childClass;
childStruct.MyParentIs();
childClass.MyParentIs();
cout << "childStruct's pareint is " << childStruct.parent;
cout << "childClass's parent is " << childClass.parent; <<=== 오류
return 0;
}
Struct는 따로 명시하지 않아도 public이기 때문에 자식 클래스에서 부모클래스 접근이 가능하고
또 외부에서 접근도 가능하다.
Class는 기본이 private이 때문에 자식 클래스에서 부모클래스를 접근하기 위해선
부모클래스의 멤버가 public or protected 이어야 한다.
상속을 받을 때도 마찬가지 이다. ChildClass가 MyClass를 상속받을 때 명시적으로 지정해주지 않아
private으로 상속 받게 되어 외부에서 접근이 불가능하다.
추가로.. 지역 변수로 선언된 Struct & Class 개체는 스택에 할당이 되고
new 연산자를 사용하여 동적 할당된 개체는 힙에 할당된다.
C#의 Struct와 Class의 차이
C#에서 Struct와 Class의 차이는 더 크다.
C#에서는 Struct는 상속할 수 없고,
Class는 상속 접근 지정자를 명시적으로 설정할 수 없다.
상속 시 부모클래스의 public 및 protected 멤버에 접근 할 수 있다.
기본적으로 둘다 멤버 접근은 모두 private이고
Struct와 Class 자체 접근은 같은 어셈블리 내에서 자유롭게 접근 가능하다.
멤버 기본 접근 지정자 |
기본 자체 접근 지정자 |
Type | 상속 | |
Struct | private | internal | 값 타입 Value Type |
struct, class 상속 x Interface 구현 o |
Class | private | internal | 참조 타입 Reference Type |
class 상속 o Interface를 통한 다중 상속 o |
Struct는 값타입이다. 데이터를 직접 저장하는 타입으로 주로 스택에 메모리 할당된다.
구조체를 복사 시 값자체가 복사가 된다.
int, bool, char와 같은 값타입이므로 null 값을 가질 수 없다. .
C#에서 Struct는 다른 struct나 class를 상속받을 수 없다. 단, interface를 통한 상속은 가능하다.
Class는 참조 타입이다 . 데이터가 저장된 주소를 저장하고 주로 힙에 할당 된다.
클래스를 복사 시 참조 복사가 되어 같은 위치의 데이터를 가리키는 것이다.
arr, string, interface, delegata와 같은 참조 타입으로 null 값을 가질 수 있다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
ChildClass myClhildClass = new ChildClass();
myClhildClass.CallPublicParent();
myClhildClass.CallProtectedParent();
myClhildClass.CallPrivateParent();
}
}
class MyClass {
string parent = "parent class";
public void PublicCallMyName() {
Console.WriteLine("Public : I am parent class");
}
protected void ProtectedCallMyName() {
Console.WriteLine("protected : I am parent class");
}
void CallMyName() {
Console.WriteLine("private : I am parent class");
}
}
class ChildClass : MyClass {
public void CallPublicParent() {
PublicCallMyName();
}
public void CallProtectedParent() {
ProtectedCallMyName();
}
public void CallParent() {
CallMyName();
}
}
}
접근 지정자를 명시적으로 지정하지 않은 멤버 함수에 접근하려고 하면 오류 메세지를 띄운다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
MyClass class1 = new MyClass();
class1.name = "Class1";
MyStruct struct1 = new MyStruct();
struct1.name = "Struct1";
MyClass class2 = class1;
class2.name = "Class2";
MyStruct struct2 = struct1;
struct2.name = "Struct2";
Console.WriteLine($"Class 1 name : {class1.name}");
Console.WriteLine($"Class 2 name : {class2.name}");
Console.WriteLine($"Struct 1 name : {struct1.name}");
Console.WriteLine($"Struct 2 name : {struct2.name}");
}
}
public class MyClass
{
public string name;
}
public struct MyStruct
{
public string name;
}
}
class는 참조를 전달하여 Class1 과 Class2가 같은 메모리 주소를 가리켜 둘 다 이름이 변했지만
struct는 복사가 되어 struct 1과 struct 2가 다른 개체이다.
Class -> Struct로 변경할 때 발생할 수 있는 문제점들
C++에서 Class를 Struct로 변경할 때 발생할 수 있는 문제점은
Class와 Struct 멤버의 기본 접근 지정자가 다르게 때문에
명시적으로 지정하지 않은 멤버의 접근 지정자를 다시 수정해줘야한다.
C#의 경우 발생할 수 있는 문제가 더 크다.
참조 타입인 Class를 값 타입인 Struct로 변경하게 되면
개체가 모두 메모리의 스택 영역에 할당되어 스택에 할당된 메모리가 초과되면
스택 오버 플로우 (Stack Overflow)가 발생 할 수 있다.
Class는 참조 타입이어서 메모리의 주소가 전달 되지만 Struct는 값 타입이어서 전달될 때 복사본이 생성된다.
Class와 다르게 Struct는 기본 생성자를 자동으로 만들어주지 않는다.
Struct 내의 모든 필드를 명시적으로 초기화 해줘야한다.
Struct는 상속이 되지않는다. 상속관계의 Class를 Struct로 변경한다면
많은 오류가 생길 것이다.
Struct는 Interface를 통해 상속이 가능하지만 이 과정 박싱과 언박싱이 발생한다.
Boxing의 경우 값타입을 참조타입으로 변환하는 과정으로 값 타입이 힙 메모리에 복사되고
그 주소가 참조 타입의 변수에 할당 된다.
Unboxing의 경우 박싱된 개체를 다시 원래의 값으로 변환하는 과정으로 힙 메모리에 저장된
값 타입 데이터를 스택으로 복사한다.
아래 코드에서 인터페이스 IMyInterface에 myStruct를 할당한 뒤 mySturct의 name에 값을 저장 후
myInterface에서 MyName함수를 호출하면 원하는 결과가 나오지 않는다. Interface는 참조 형식일텐데 말이다.
그 이유는 값 형식을 참조 형식에 저장하면서 박싱이 일어난다.
IMyInterface에 할당된 데이터는 복제되어 힙 메모리에 저장되었기 때문이다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
MyStruct myStruct = new MyStruct();
IMyInterface myInterface = myStruct;
myStruct.name = "Struct";
myStruct.MyName();
myInterface.MyName();
}
}
interface IMyInterface
{
void MyName();
}
struct MyStruct : IMyInterface
{
public string name;
public void MyName()
{
Console.WriteLine($"My name is {name}");
}
}
}
class를 struct로 변경하게 될 경우 다양한 문제점이 발생한다.
class와 struct는 메모리 사용 공간이 다르고 Garbage Collector의 관리 여부 또한 다르기 때문에
적정한 형식을 사용하는 것이 중요하다.
'C++' 카테고리의 다른 글
C++ template VS C# generic (2) | 2024.08.08 |
---|---|
#include <memory> Smart Pointer (0) | 2024.07.01 |
SOLID - OOP 설계의 원칙 (0) | 2024.06.08 |
OOP - Object Oriented Programming 객체 지향 프로그래밍 (0) | 2024.06.03 |
std::sort() & Lamda (0) | 2024.05.14 |