내 생각을 C ++에서 C #으로 마이그레이션하는 방법


12

저는 숙련 된 C ++ 개발자이며, 언어를 자세하게 알고 있으며 특정 기능 중 일부를 집중적으로 사용했습니다. 또한 OOD의 원칙과 디자인 패턴을 알고 있습니다. 나는 지금 C #을 배우고 있지만 C ++ 사고 방식을 제거 할 수 없다는 느낌을 멈출 수는 없습니다. 나는 C ++의 강점에 너무 열심히 묶어 일부 기능 없이는 살 수 없습니다. 그리고 C #에서 좋은 해결 방법이나 대체품을 찾을 수 없습니다.

어떤 모범 사례 , 디자인 패턴 , 숙어 C ++ 관점에서 C #에서 다른 당신이 제안 할 수 있습니다? C #에서 바보처럼 보이지 않는 완벽한 C ++ 디자인을 얻는 방법은 무엇입니까?

특히, (최근 예)를 다루는 좋은 C # -ish 방법을 찾을 수 없습니다.

  • 결정적인 정리 (예 : 파일)가 필요한 자원의 수명을 제어합니다. 이것은 using손에 들기 는 쉽지만 리소스의 소유권이 이전 될 때 올바르게 사용하는 방법은 무엇입니까? C ++에서는 공유 포인터를 사용하고 적절한 시점에 '가비지 수집'을 처리하게합니다.
  • 특정 제네릭에 대한 재정의 함수로 끊임없이 어려움을 겪고 있습니다 (C ++의 부분 템플릿 전문화와 같은 것을 좋아합니다). C #에서 일반 프로그래밍을 시도하는 것을 포기해야합니까? 어쩌면 제네릭은 의도적으로 제한되어 있으며 특정 문제 영역을 제외하고는 그것을 사용하는 것이 C #이 아닙니다?
  • 매크로와 유사한 기능. 일반적으로 나쁜 생각이지만 일부 문제 영역에는 다른 해결 방법이 없습니다 (예 : 디버그 릴리스로만 이동해야하는 로그와 같은 명령문의 조건부 평가). 그것들 if (condition) {...}이 없으면 더 많은 상용구 를 넣어야 하며 부작용 유발 측면에서 여전히 동일하지 않습니다.

# 1은 저에게 어려운 것이며, 스스로 답을 알고 싶습니다. # 2-간단한 C ++ 코드 예제를 제공하고 왜 필요한지 설명 할 수 있습니까? # 3의 경우- C#다른 코드를 생성 하는 데 사용하십시오 . a CSV또는 an XML또는 무엇을 입력으로 제출하고 a C#또는 SQL파일을 생성 할 수 있습니다. 기능적인 매크로를 사용하는 것보다 더 강력 할 수 있습니다.
Job

매크로와 같은 기능에 대해 어떤 구체적인 일을하려고합니까? 내가 찾은 것은 일반적으로 C ++에서 매크로로 수행 한 작업을 처리하기위한 C # 언어 구조가 있다는 것입니다. C # 프리 프로세서 지시문도 확인하십시오. msdn.microsoft.com/en-us/library/4y6tbswk.aspx
ravibhagw

C ++에서 매크로로 수행되는 많은 작업은 C #에서 리플렉션으로 수행 할 수 있습니다.
Sebastian Redl

누군가가 "올바른 직업을위한 올바른 도구-당신이 필요로하는 것에 따라 언어를 선택하십시오"라고 말할 때 내 애완 동물 친구 중 하나입니다. 범용 언어로 된 큰 프로그램의 경우 이것은 쓸모없고 도움이되지 않는 말입니다. 즉, C ++이 다른 어떤 언어보다 낫다는 것은 결정적 자원 관리입니다. 결정적 자원 관리가 프로그램의 주요 기능입니까, 아니면 익숙한 것입니까? 중요한 사용자 기능이 아닌 경우 그 기능에 지나치게 집중할 수 있습니다.
Ben

답변:


16

내 경험으로는 이것을 할 책이 ​​없습니다. 포럼은 관용구와 모범 사례에 도움이되지만 결국에는 시간을 투자해야합니다. C #에서 여러 가지 다른 문제를 해결하여 연습하십시오. 어려웠거나 어색한 것을보고 다음에 덜 어색하고 어색하지 않게하십시오. 나를 위해,이 과정은 내가 "얻기"까지 한 달 정도 걸렸다.

집중해야 할 가장 큰 것은 메모리 관리가 부족하다는 것입니다. C ++에서는 이것이 모든 단일 디자인 결정을 지배하지만 C #에서는 반 생산적입니다. C #에서는 종종 개체의 죽음 또는 다른 상태 전환 을 무시 하려고 합니다. 이러한 종류의 연관은 불필요한 결합을 추가합니다.

대부분 새로운 도구를 가장 효과적으로 사용하는 데 집중하고 이전 도구에 대한 첨부 파일에 집중하지 마십시오.

C #에서 바보처럼 보이지 않는 완벽한 C ++ 디자인을 얻는 방법은 무엇입니까?

다른 관용구가 다른 디자인 접근 방식으로 나아가고 있음을 깨닫습니다. 당신은 (대부분의) 언어들 사이에 1 : 1 무언가를 포팅 할 수없고 다른 쪽 끝에는 아름답고 관용적 인 것을 기대할 수 없습니다.

그러나 특정 시나리오에 대한 응답으로 :

관리되지 않는 리소스 공유 -C ++에서는 좋지 않은 아이디어였으며 C #에서도 나쁜 아이디어였습니다. 파일이 있으면 파일을 열고 사용하고 닫으십시오. 스레드간에 공유하는 것은 문제를 요구하는 것입니다. 실제로 하나의 스레드 만 사용하는 경우 해당 스레드를 열거 나 소유하고 닫으십시오. 당신이 경우 정말 어떤 이유로 오래 살았 파일이 다음 클래스가 있음을 나타내는하고 응용 프로그램이 필요에 따라 (가까운 종료하기 전에, 응용 프로그램 종류의 이벤트 닫기에 가까운 등 넥타이) 것을 관리 할 수

부분 템플릿 전문화 -C #을 많이 사용할수록 C ++에서 템플릿 사용이 YAGNI에 더 많이 사용된다는 것을 알았지 만 운이 좋지 않습니다. T가 항상 int 일 때 왜 일반적인 것을 만드는가? 다른 시나리오는 C #에서 인터페이스를 자유롭게 적용하여 가장 잘 해결됩니다. 인터페이스 또는 델리게이트 유형이 변경하려는 내용을 강력하게 입력 할 수있는 경우 제네릭이 필요하지 않습니다.

전 처리기 조건 -C #에는 #if, 고개가 있습니다. 더 좋은 점은 컴파일러 심볼이 정의되지 않은 경우 코드에서 해당 기능을 단순히 삭제하는 조건부 속성 을 갖습니다 .


리소스 관리와 관련하여 C #에서는 관리자 클래스 (정적 또는 동적 일 수 있음)를 갖는 것이 일반적입니다.
rwong

공유 리소스 관련 : C #에서는 관리되는 리소스가 용이합니다 (경쟁 조건과 관련이없는 경우) 관리되지 않는 리소스는 훨씬 초기 단계입니다.
중복 제거기

@rwong-일반적이지만 관리자 클래스는 여전히 피해야 할 반 패턴입니다.
Telastyn

메모리 관리와 관련하여 C #으로 전환하면 특히 처음에 더 어려워졌습니다. C ++보다 더 잘 알아야한다고 생각합니다. C ++에서는 모든 객체가 언제, 어디서 할당되고 할당이 해제되고 참조되는지 정확하게 알고 있습니다. C #에서는이 모든 것이 숨겨져 있습니다. C #에서는 여러 번 객체에 대한 참조가 얻어지고 메모리 누수가 시작된다는 것을조차 알지 못합니다. C #에서 메모리 누수를 추적하는 것이 재미 있습니다. 일반적으로 C ++에서 알아내는 것이 쉽지 않습니다. 물론 경험이 있다면 C #의 이러한 미묘한 차이를 배워서 문제가 덜되는 경향이 있습니다.
Dunk

4

내가 알 수 있듯이, 결정적 평생 관리를 허용하는 유일한 것은 IDisposable(언어 또는 유형 시스템 지원이없는 것처럼) 해당 리소스의 소유권을 양도해야 할 때 세분화됩니다.

C # atm을하는 C ++ 개발자. -C # 유형 / 서명에서 소유권 이전 (파일, db-connections 등) 모델에 대한 지원 부족은 매우 성가신 일이지만, "그냥"의존하기 위해서는 꽤 괜찮은 것으로 인정해야합니다. 컨벤션 및 API 문서 에서이 권한을 얻으십시오.

  • 필요한 경우IDisposable 결정 론적 정리를 수행하는 데 사용하십시오 .
  • 의 소유권을 양도해야하는 경우 IDisposable관련 API를 명확하게 using하고이 경우에는 사용 using하지 않아야합니다. "취소"할 수 없기 때문 입니다.

그렇습니다. 현대 C ++ (이동 sematics 등)을 사용하여 유형 시스템에서 표현할 수있는 것만 큼 우아하지는 않지만 작업이 완료되었으며 C # 커뮤니티 전체가 (놀랍게도) AFAICT.


2

두 가지 특정 C ++ 기능을 언급했습니다. RAII에 대해 설명하겠습니다.

C #은 가비지 수집되므로 일반적으로 적용 할 수 없습니다. 가장 가까운 C # 구성은 아마도 IDisposable다음과 같이 사용되는 인터페이스 일 것입니다 .

using (FileStream fs = File.OpenRead(@"c:\path\filename.txt"))
{
   //Do stuff with the file.
} //File will be closed here.

IDisposable관리되는 리소스가 필요하지 않기 때문에 자주 구현할 필요가 없으며 그렇게하는 것도 약간 앞당겨 야합니다. 그러나 구현하는 데에는 using구문을 사용해야합니다 (또는 실행 불가능한 Dispose경우 직접 호출 using) IDisposable. 이것을 엉망으로 만들더라도 잘 디자인 된 C # 클래스는 (파이널 라이저를 사용하여) 이것을 처리하려고 시도합니다. 그러나 가비지 수집 언어에서는 RAII 패턴이 제대로 작동하지 않으므로 (수집이 비 결정적이므로) 리소스 정리가 지연 될 수 있습니다 (때로는 치명적일 수 있음).


RAII는 실제로 가장 큰 문제입니다. 하나의 스레드에 의해 생성되고 다른 스레드에 제공되는 리소스 (예 : 파일)가 있다고 가정 해보십시오. 스레드가 더 이상 필요하지 않은 특정 시간에 처리되도록하려면 어떻게해야합니까? 물론 Dispose를 호출 할 수는 있지만 C ++의 공유 포인터보다 더 나빠 보입니다. 거기에는 RAII가 없습니다.
Andrew

@Andrew 설명하신 내용은 소유권 이전을 의미하는 것 같습니다. 이제 두 번째 스레드가 Disposing()파일을 담당하므로 아마도을 사용해야 using합니다.
svick

@Andrew-일반적으로 그렇게해서는 안됩니다. 대신 스레드에게 파일을 얻는 방법을 알려주고 수명의 소유권을 갖도록해야합니다.
Telastyn

1
@Telastyn 관리 리소스에 대한 소유권을 포기하는 것이 C ++보다 C #에서 더 큰 문제라는 것을 의미합니까? 놀랍습니다.
Andrew

6
@Andrew : 요약하자 : C ++에서는 모든 리소스에 대해 균일하고 반자동 관리가 가능합니다. C #에서는 메모리를 완전히 자동으로 관리하고 다른 모든 것을 완전히 수동으로 관리합니다. 실제 변경은 없으므로 "이 문제를 어떻게 해결합니까?"가 아니라 "어떻게 익숙해 지나요?"
Jerry Coffin

2

많은 C ++ 개발자가 끔찍한 C # 코드를 작성하는 것을 보았으므로 이것은 매우 좋은 질문입니다.

C #을 "더 좋은 구문을 가진 C ++"로 생각하지 않는 것이 가장 좋습니다. 그들은 당신의 생각에 다른 접근법을 요구하는 다른 언어입니다. C ++은 CPU와 메모리가 무엇을할지 끊임없이 생각하도록합니다. C #은 그렇지 않습니다. C #은 CPU와 메모리에 대해 생각하지 않고 작성 하려는 비즈니스 영역 에 대해 생각하도록 특별히 설계되었습니다 .

내가 본 이것의 한 예는 많은 C ++ 개발자들이 foreach보다 빠르기 때문에 for 루프를 사용하고 싶어한다는 것입니다. 이것은 반복되는 컬렉션의 가능한 유형 (따라서 코드의 재사용 성과 유연성)을 제한하기 때문에 일반적으로 C #에서 나쁜 생각입니다.

C ++에서 C #으로 다시 조정하는 가장 좋은 방법은 다른 관점에서 코딩을 시도하고 접근하는 것입니다. 수년에 걸쳐 여러분이 작성하는 코드를 필터링하기 위해 두뇌에서 "CPU와 메모리가 무엇을하고 있는지"스레드를 사용하는 데 익숙해지기 때문에 처음에는 이것이 어려울 것입니다. 그러나 C #에서는 비즈니스 도메인의 개체 간 관계에 대해 생각해야합니다. "컴퓨터가 무엇을하고 있는가"와 달리 "무엇을하고 싶은가"

목록에 for 루프를 작성하는 대신 객체 목록의 모든 작업을 수행하려면 루프를 IEnumerable<MyObject>사용하고 사용하는 메소드를 작성하십시오 foreach.

구체적인 예 :

결정적인 정리 (예 : 파일)가 필요한 자원의 수명을 제어합니다. 이것은 손에 사용하는 것이 쉽지만 리소스의 소유권이 이전 될 때 올바르게 사용하는 방법은 무엇입니까? C ++에서는 공유 포인터를 사용하고 적절한 시점에 '가비지 수집'을 처리하게합니다.

C #에서는 (특히 스레드 간)이 작업을 수행해서는 안됩니다. 파일에 무언가를해야하는 경우 한 번에 한 곳에서 수행하십시오. 클래스간에 전달되는 관리되지 않는 리소스를 관리하는 래퍼 클래스를 작성하는 것이 좋지만 스레드간에 File을 전달하거나 전달하지 않고 별도의 클래스에 쓰기 / 읽기 / 닫기 / 열기를하십시오. 관리되지 않는 리소스의 소유권을 공유하지 마십시오. Dispose패턴을 사용하여 정리를 처리 하십시오 .

특정 제네릭에 대한 재정의 함수로 끊임없이 어려움을 겪고 있습니다 (C ++의 부분 템플릿 전문화와 같은 것을 좋아합니다). C #에서 일반 프로그래밍을 시도하는 것을 포기해야합니까? 어쩌면 제네릭은 의도적으로 제한되어 있으며 특정 문제 영역을 제외하고는 그것을 사용하는 것이 C #이 아닙니다?

C #의 제네릭은 제네릭으로 디자인되었습니다. 제네릭의 특수화는 파생 클래스에서 처리해야합니다. List<T>a List<int>또는 a 인 경우 왜 다르게 동작 해야 List<string>합니까? List<T>에 적용되는 모든 작업 은 일반에 적용됩니다 List<T>. 의 .Add메소드 동작을 변경하려면 내부에서 일반을 ​​사용하지만 다른 방식으로 작업을 수행 List<string>하는 파생 클래스 MySpecializedStringCollection : List<string>또는 구성된 클래스 를 만듭니다 MySpecializedStringCollection : IList<string>. 이를 통해 Liskov 대체 원칙을 위반하지 않고 수업을 사용하는 다른 사람들을 엄청나게 조이는 것을 피할 수 있습니다.

매크로와 유사한 기능. 일반적으로 나쁜 생각이지만 일부 문제 영역의 경우 다른 해결 방법이 없습니다 (예 : 디버그 릴리스로만 이동해야하는 로그와 같은 명령문의 조건부 평가). 그것들을 갖지 못한다는 것은 if (condition) {...} 상용구를 더 넣어야하고 부작용 유발 측면에서 여전히 동일하지 않다는 것을 의미합니다.

다른 사람들이 말했듯이 전 처리기 명령을 사용하여이 작업을 수행 할 수 있습니다. 속성이 더 좋습니다. 일반적으로 속성은 클래스의 "핵심"기능이 아닌 것을 처리하는 가장 좋은 방법입니다.

간단히 말해서 C #을 작성할 때는 CPU와 메모리가 아니라 비즈니스 영역에 대해서만 생각해야합니다. 필요한 경우 나중에 최적화 할 수 있지만 코드는 매핑하려는 비즈니스 원칙 간의 관계를 반영해야합니다.


3
WRT. 자원 이전 : " 이러면 안됩니다 " 라고 IMHO를 자르지 않습니다. 종종, 아주 좋은 디자인은 전송 제안 IDisposable( 하지 다른 하나 개의 클래스에서 원시 자원) : 그냥보고 에서는 StreamWriter 이가 완벽한 의미가 API Dispose()전달 된 스트림을. 그리고 C # 언어에는 구멍이 있습니다-타입 시스템에서는 이것을 표현할 수 없습니다.
Martin Ba

2
"이것의 한 예는 많은 C ++ 개발자들이 foreach보다 빠르기 때문에 for 루프를 사용하고 싶어한다는 것입니다." 이것은 C ++에서도 좋지 않은 생각입니다. 비용이 들지 않는 추상화는 C ++의 강력한 기능이며, 대부분의 경우 foreach 또는 기타 알고리즘이 수작업 for 루프보다 느리지 않아야합니다.
Sebastian Redl

1

나를 위해 가장 다른 것은 모든 것을 새롭게하는 경향이었다 . 객체를 원한다면 루틴에 전달하고 기본 데이터를 공유하는 것을 잊지 마십시오. C # 패턴은 새로운 객체를 만드는 것입니다. 이것은 일반적으로 C # 방식보다 훨씬 더 많은 것으로 보입니다. 처음에는이 패턴이 매우 비효율적으로 보였지만 C #에서 대부분의 개체를 만드는 것이 빠른 작업이라고 생각합니다.

그러나 매번 자주 사용하기 때문에 하나만 만들려고 할 때마다 나를 괴롭히는 정적 클래스도 있습니다. 어느 것을 사용 해야할지 쉽게 알 수 없습니다.

C # 프로그램에서 더 이상 필요하지 않을 때 무시하는 것이 매우 기쁩니다.하지만 객체에 포함 된 내용 만 기억하십시오. 일반적으로 메모리에 구성되어있는 '특이하지 않은'데이터가 있는지 생각하면됩니다. 그냥 저절로 사라지거나 다른 사람들은 폐기해야합니다. 그렇지 않으면 수동으로 또는 사용하지 않는 블록으로 파괴하는 방법을 살펴 봐야합니다.

C #은 VB와 거의 다르지 않으므로 동일한 RAD 도구로 간주 할 수 있습니다 (CB를 VB.NET과 같이 생각하면 도움이된다면 구문은 약간만 나타납니다) VB를 사용하던 시절은 RAD 개발을위한 올바른 사고 방식으로 전환되었습니다. 유일한 어려운 점은 사용해야 할 거대한 .net 라이브러리의 비트를 결정하는 것입니다. Google은 가장 확실한 친구입니다!


1
C #에서는 적어도 구문 관점에서 객체를 루틴에 전달하여 기본 데이터를 공유하는 것이 좋습니다.
Brian
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.