O nadużywaniu C++/CLI

Odpowiedz Nowy wątek
2012-04-09 22:58
Rev
18

@Azarien napisał dzisiaj, że Nie ma powodu, by C++/CLI musiało być tylko do „portowania bibliotek” poza tym, że wielu tak uważa. Chciałbym poruszyć ten wątek, a że w tamtym w gruncie rzeczy mowa o innym języku to założyłem nowy.

Ostatnimi czasy widać w Polsce zwiększające się zainteresowanie językiem C++/CLI. Jak dla mnie, wynika to z niczego innego jak próbą pójścia na skróty w nauce czystego C++. Wiadomo: czytelniejsza biblioteka standardowa, a klepanie interfejsu użytkownika w Visual Studio nigdy nie było prostsze. Koniec końców najczęściej jest taki, że przychodzi człowiek na forum, bo nie może sobie poradzić ze składnią tego tworu i okazuje się, że nie poznał ani C++, ani .NET.

Także apeluję do młodszych (stażem, bo sam młody wiekiem jestem) programistów: nie zabierajcie się za C++/CLI, jeżeli chcecie się nauczyć C++ oraz nie zabierajcie się za C++/CLI, jeżeli chcecie nauczyć się .NET.

W pierwszym przypadku uczcie się po prostu C++. Po dogłębnym przestudiowaniu wcale niedużej biblioteki standardowej ze zdziwieniem stwierdzicie na koniec, że i tak zaoszczędzi wam kupę czasu, bo przykładowo nie trzeba już korzystać z ręcznie klepanych pętli czy mieszać gdzieś w kod bibliotekę C do operowania na napisach, bo nigdy wcześniej nie spodziewaliście się, że nagłówek <algorithm>, iteratory, insertery, i/o stringstreamy mogą być tak proste i przydatne. Można pójść dalej i poznać nowe (w standardzie) inteligentne wskaźniki i na zawsze zrezygnować z ręcznego zarządzania pamięci.

Jeżeli z kolei podoba wam się przeciąganie kontrolek na formę w Visual Studio i nie macie odgórnego wymogu (ze szkoły, uczelni) na naukę C++ to bezwzględnie piszcie od razu w C#. O ile na samiutkim początku może zbyt dużej różnicy nie ma, bo kompilator i tak wygeneruje kod za was, to już za chwilę zababracie się zupełnie w C++/CLI. Nie jest to wcale dziwne, bo nie dość, że C++ jest językiem skomplikowanym to dołączenie do niego zawiłości CLR z niedoskonałą (w porównaniu do C#, gdzie twórcy języka mogli zaprojektować go od zupełnych podstaw) składnią prowadzi do katastrofy. A idzie nam jeszcze Windows 8, WinRT i kolejna odmiana C++, czyli C++/CX z podobnymi, ale znów innymi i prowadzącymi do kolejnych nieporozumień rozszerzeniami.

Spójrzcie z resztą na tą bardzo prostą klasę, którą napisałem jakiś czas temu w C#.

namespace Rev.PhotoAlbum.Application.Modules
{
    using Autofac;

    using NHibernate;
    using NHibernate.Cfg;

    using Rev.PhotoAlbum.Model.Configuration;
    using Rev.PhotoAlbum.Model.Repositories;

    public class DataAccessModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            var configuration = Configurator.BuildConfiguration();
            var sessionFactory = Configurator.BuildSessionFactory(configuration);

            RegisterComponents(builder, configuration, sessionFactory);
        }

        private void RegisterComponents(ContainerBuilder builder, Configuration configuration, ISessionFactory sessionFactory)
        {
            builder.RegisterInstance(configuration).As<Configuration>().SingleInstance();
            builder.RegisterInstance(sessionFactory).As<ISessionFactory>().SingleInstance();
            builder.Register(x => x.Resolve<ISessionFactory>().OpenSession()).As<ISession>().InstancePerLifetimeScope();

            builder.RegisterAssemblyTypes(typeof(IRepository<>).Assembly)
                .Where(t => t.IsClosedTypeOf(typeof(Repository<,>)))
                .AsClosedTypesOf(typeof(IRepository<>));
        }
    }
}

Nie ma co prawda w kodzie komentarzy, ale dzięki odpowiedniej konstrukcji frameworków, z których korzystam kod ten można.. po prostu przeczytać - ciągiem. Układają się z tego zdania i na pierwszy rzut oka można się domyślić co się dzieje - chodzi mi zwłaszcza o metodę RegisterComponents. Jeżeli ktoś nigdy z AutoFaca nie korzystał, a ma minimalną wiedzę z teorii na temat kontenerów IoC mógłby własny kod napisać po jedno, dwu-krotnym przeczytaniu mojego.

A teraz ten sam kod w C++/CLI.
Najpierw oczywiście plik nagłówkowy, jako sumienni programiści C++ nie wrzucamy wszystkiego do jednego pliku.

#pragma once

namespace Rev
{
    namespace PhotoAlbum
    {
        namespace Application
        {
            namespace Modules
            {
                ref class DataAccessModule : public Autofac::Module
                {
                private:
                    void RegisterComponents(Autofac::ContainerBuilder^ builder, NHibernate::Cfg::Configuration^ configuration, NHibernate::ISessionFactory^ sessionFactory);
                protected:
                    virtual void Load(Autofac::ContainerBuilder^ builder) override;
                };
            }
        }
    }
}

Ah, no tak. C++ nie obsługuje zagnieżdżonych przestrzeń nazw w jednej instrukcji, musimy rozbić je na kilka. Tutaj pedantycznie trzymam się formatowania, które sobie przyjąłem, możemy zawsze wrzucić wszystkie namespace'y do jednej linijki albo - jak polecają w internecie - stworzyć sobie makro. A jak.
Rozciągnął się kod niesamowicie, no, ale przecież mamy na uwadze zasadę nieużywania using namespace w plikach nagłówkowych, żeby nie potworzyć żadnych konfliktów nazw w plikach, gdzie będziemy nasz plik nagłówkowy dołączać.

using namespace Rev::PhotoAlbum::Application::Modules;

using namespace Autofac;

using namespace NHibernate;
using namespace NHibernate::Cfg;

using namespace Rev::PhotoAlbum::Model::Configuration;
using namespace Rev::PhotoAlbum::Model::Repositories;

void DataAccessModule::Load(ContainerBuilder^ builder)
{
    auto configuration = Configurator::BuildConfiguration();
    auto sessionFactory = Configurator::BuildSessionFactory(configuration);

    RegisterComponents(builder, configuration, sessionFactory);
}

ISession^ ISessionResolve(IComponentContext^ context)
{
    return ResolutionExtensions::Resolve<ISessionFactory^>(context)->OpenSession();
}

bool IRepositoryWhereCondition(System::Type^ type)
{
    return TypeExtensions::IsClosedTypeOf(type, Repository::typeid);
}

void DataAccessModule::RegisterComponents(ContainerBuilder^ builder, Configuration^ configuration, ISessionFactory^ sessionFactory)
{
    RegistrationExtensions::RegisterInstance(builder, configuration)->As<Configuration^>()->SingleInstance();
    RegistrationExtensions::RegisterInstance(builder, sessionFactory)->As<ISessionFactory^>()->SingleInstance();

    RegistrationExtensions::Register<ISession^>(builder, gcnew System::Func<IComponentContext^, ISession^>(&ISessionResolve))->As<ISession^>()->InstancePerLifetimeScope();

    RegistrationExtensions::AsClosedTypesOf(RegistrationExtensions::Where(RegistrationExtensions::RegisterAssemblyTypes(builder, IRepository::typeid->Assembly),
        gcnew System::Func<System::Type^, bool>(&IRepositoryWhereCondition)), IRepository::typeid);
}

Chciałbym zwłaszcza zwrócić na końcówkę metody RegisterComponents. Konia z rzędem temu, kto rozszyfruje co się tutaj dzieje. Niestety (albo stety) bardzo wiele frameworków, które ułatwiają (taa) pisanie w .NET korzystają z metod rozszerzających (z resztą zdaje się, że bardzo dobrze, zgodnie z zasadą open/closed). Z jakiś powodów najzwyczajniej nie są one składniowo obsługiwane w C++/CLI i trzeba wywoływać je ręcznie, z klasy, w której są zdefiniowane i przekazywać ręcznie obiekt. Bez tracenia dodatkowego czasu na znalezienie chociażby nazwy tej klasy się nie obejdzie. Kolejna rzecz to lambdy, które w C# niesamowicie ułatwiają sprawę, a w C++/CLI powodują, że musimy je wydzielić do zewnętrznej metody. Lambdy z C++11 nawet w Visual Studio 11 nie są z nimi kompatybilne. No trudno.
Ktoś mógłby powiedzieć, że klepanie nawet takiego kodu z IntelliSense nie będzie trudne. Nawet dodali do niego obsługę C++/CLI. Bardziej przykra sprawa jest taka, że na co trzeciej bardziej rozbudowanej linijce z typami generycznymi po prostu się wykłada i zaczyna krzyczeć o błędach. Których de facto nie ma, bo kod się kompiluje i działa.

Wykorzystanie w C#:

var builder = new ContainerBuilder();
builder.RegisterModule<Modules.DataAccessModule>();
var container = builder.Build();
var repository = container.Resolve<IRepository<Person>>();

I C++/CLI:

auto builder = gcnew ContainerBuilder();
RegistrationExtensions::RegisterModule<Modules::DataAccessModule^>(builder);
auto container = builder->Build(Builder::ContainerBuildOptions::Default);
auto repository = ResolutionExtensions::Resolve<IRepository<Person^>^>(container);

Nie domyślił się wartości domyślnej parametru, a IntelliSense rzucił malowniczy błąd w ostatniej linii o tym, że wywołanie pasuje do wielu przeładowań oraz jednocześnie ma za mało parametrów.

Nie mówię, że pisanie w C++/CLI nie ma w ogóle sensu. Ma, ale są to naprawdę marginalne zastosowania, gdy nie wystarcza nam P/Invoke i interop w C#. Ja jeszcze nigdy nie musiałem go naprawdę użyć. Wy pewnie też nie.


To nie zawsze jest tak fajnie. Większość ludzi poznaje programowanie na studiach (bądź w TI - ale tu jest sporo godzin w cyklu kształcenia na PSiO i program nie jest zbyt rozbudowany. Jestem akurat świadkiem tego jak na Politechnice Poznańskiej wyglądają zajęcia z <i>programowani</i>. I semestr fizyk techniczna, w sześciu 90 minutowych wykładam omówiona całość składni C++ (włączywszy takie szczegóły jak wskaźniki na funkcje, wskaźniki na składowe, sporo funkcji z <code>cstring, stdio, stdlib</code>. No i super bo ludzie nic nie wiedzą, nic a wykładowca zadowolony. - szasza 2013-01-23 16:55
Na ćwiczeniach... prowadzący pisze kod, rzuca na ekran. 5 min na przepisanie i "to już wiecie z grubsza o co tutaj chodzi". Tyle w I sem. Na drugim roku, fizyka komputerowa. Po takich wątpliwych postawach C++, od razu rzut na C++/CLI. Gierki typu obsługa zdarzenia i poruszanie piłeczką aby trafić w uciekającą w losowe miejsca na ekranie bramkę. Metoda uczenia? "Otwieracie nowy projekt i na moim komputerze macie plik, z niego kopiujecie, jeśli działa to idziemy dalej". Jeśli nie działa to "szukacie błędów" a jak ludzie mają ich szukać jak nie rozumieją większości kodu... - szasza 2013-01-23 16:57

Pozostało 580 znaków

2012-04-11 08:21
2

Podany przykład jest wybitnie tendencyjny, bo opiera się na tym, że C++/CLI nie ma extension methods. Gdyby Microsoft w końcu zaimplementował rozszerzenia pod C++/CLI, ten argument by znikł.

Można również pokazać argument odwrotny, jakiegoś kodu używającego biblioteki napisanej w C++, najlepiej obiektowo z szablonami, i jej użycie spod C++/CLI i spod C#.

Ubolewam jednak, że M$ od kilku wersji kompletnie olewa C++/CLI tłumacząc się „brakiem zasobów”, w rezultacie czego język stanął na poziomie .Net 2.0. Z tego powodu użycie konstrukcji specyficznych dla nowszych wersji frameworka jest skomplikowane albo niemożliwe.
To nie jest wina języka, ale mizernego jego rozwoju.

edytowany 1x, ostatnio: Azarien, 2012-04-11 08:23

Pozostało 580 znaków

2012-04-13 13:14
2

Pytanie do czego przydaje się w ogóle C++/CLI? Bo poza celowym łączeniem kodu zarządzanego i niezarządzanego jakoś zastosowań nie widzę. Sam bym myślał o użyciu C++/CLI tylko do:

  • napisania biblioteki dll, która będzie wrapperem importującym wiele funkcji natywnych lub klas z natywnej bibloteki .dll. Potem ten wrapper używany w projekcie w C# jako normalne assembly. O ile mi wiadomo, jeśli niezarządzana biblioteka dll eksportuje bezpośrednio klasy to użycie jej w C# bezpośrednio może okazać się niemożliwe
  • uzyskania sytuacji odwrotnej: mam projekt w natywnym C++, np mfc albo C++ Builder i zachciało mi się użyć funkcjonalności z .net. Robię wrappera, który eksportuje niezarządzane funkcje możliwe do użycia w projekcie w C++ niezarządzanym.

Ale po jaką cholerę pisać całe aplikacje w C++/CLI - tego nie rozumiem. Zresztą, pomijając to wszystko co zostało już powiedziane, jedyne co osiągniemy to aplikacja w WinForms z pewnymi ograniczeniami, nie ma wpf, nie ma webserwisów itd itp.

Pozostało 580 znaków

2012-04-13 14:08
1

napisania biblioteki dll, która będzie wrapperem importującym wiele funkcji natywnych lub klas z natywnej bibloteki .dll
Biblioteka może być wielka, specjalistyczna, używać konstrukcji typowych dla C++. Samo napisanie wrappera może być zbyt dużym zadaniem, większym niż zaciśnięcie zębów i napisanie GUI w C++/CLI.

C++/CLI jest dla mnie najbardziej naturalnym wyborem jeśli mam na szybko napisać aplikację GUI, pod Windows, w C++.
Prawie w C++, powiedzmy.

Jeśli jednak nie ma argumentów przemawiających za C++, wybieram C# naturalnie.

edytowany 1x, ostatnio: Azarien, 2012-04-13 15:41

Pozostało 580 znaków

2012-04-13 14:15
0

Biblioteka może być wielka, specjalistyczna, używać konstrukcji typowych dla C++

Może. Ale praktyka pokazuje, że często jest tak że potrzebujemy użyć tylko niewielkiej części dostępnych funkcjonalności, co łatwo ubrać w metody które da się potem wykorzystać w C#. Mowa oczywiście nie o setkach plików .cpp tylko np. zamkniętej bibliotece dll napisanej w c++, która eksportuje różne rzeczy.

Zreszta, zaciskanie zębów nic nie pomoże jeśli np. chcemy wpf zamiast przestarzałego winformsa.

edytowany 1x, ostatnio: othello, 2012-04-13 14:16
na czym polega problem z WPF? „niedasie” z powodu jakich magicznych mechanizmów, czy po prostu brak odpowiedniego designera w Visualu? - Azarien 2012-04-13 15:29
No jeśli chcesz rzeźbić ręcznie to może i się da, ale czy to ma jakiś sens w ogóle? Bo nie wydaje mi się. - othello 2012-04-13 18:17
sensu pewnie nie, bo przecież jest WinForms. ale „rzeźbiłem” już w ten sposób LINQ, WCF i Solver Foundation. wszystko działa, mimo że miejscami wygląda jak pokazany kod Reva, albo jeszcze gorzej. - Azarien 2012-04-13 19:34

Pozostało 580 znaków

2012-04-13 19:09
0
Azarien napisał(a)

C++/CLI jest dla mnie najbardziej naturalnym wyborem jeśli mam na szybko napisać aplikację GUI, pod Windows, w C++.

Ale po co pisać aplikację GUI pod Windows w udziwnionym C++? Jakiś praktyczny przykład?


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."
Jest biblioteka w C++, jedyna w swoim rodzaju. Trzeba zrobić proste GUI. Nie chce mi się pisać wrappera, do którego i tak trzeba będzie napisać GUI. - Azarien 2012-04-13 19:37

Pozostało 580 znaków

2012-04-16 08:53
0

Pytacie czemu ludzie zaczynają od C++/CLI zamiast C#? Ano przez takie wpisy np: http://www.dobreprogramy.pl/P[...]ostego-kalkulatora,31485.html - pytanie PO CO było to robić w C++ zamiast w C#?

edytowany 1x, ostatnio: othello, 2012-04-16 08:54
Ktoś może znać C++. Nie znać C#. Chce napisać aplikacyjkę, wybiera Visual Studio, zapewne Express. Wydaje mu się, że skoro zna C++ to od razu sobie poradzi, a nie musi się uczyć całego nowego języka C#. Jest to złudne, bo żeby pisać w C++/CLI i tak trzeba znać C#. - Azarien 2012-04-16 10:03
Jeśli ktoś zna C++ to przejście na C# nie jest trudne. Wręcz przeciwnie - na każdym kroku przekonujesz się to, czego brakowało ci w C++, jest w C#. - othello 2012-04-16 11:44
Albo ktoś może nie czuć potrzeby przechodzenia na C#, bo jest Java, Objective-C, Scala. Albo ktoś po prostu lubi robić przenośny kod. Albo ma fanaberię pisania w C++ na iSeries / OS/390... - vpiotr 2013-11-07 22:59

Pozostało 580 znaków

2013-11-02 13:56
1

No,właśnie C# jest następcą C++,bo C# zostało stworzone w 2000 a C++ w 1979
Dane wzięte żywcem z wikipedii :)

Nie, nie jest. - somekind 2013-11-02 17:53
A ja jestem następcą Napoleona... - aurel 2013-11-05 21:45
Nie jest. Czytałeś może o tym że ma wyjść C++14? - CSharp 2015-10-10 15:55
@CSharp gwoli ścisłości, C++14 wyszło już jakiś czas temu, teraz szykuje się C++17 - spartanPAGE 2015-10-10 15:57

Pozostało 580 znaków

2015-05-31 12:11
0

Zastanawiające, że ostatnio mamy wysyp pytań na forum dotyczących Windows Forms w C++/CLI.

I to w VS2013, gdzie nie da się przypadkiem kliknąć takiego typu projektu, bo go nie ma - trzeba wiedzieć że coś takiego istnieje i albo otworzyć projekt utworzony pod VS2010, albo stworzyć projekt konsolowy i pozmieniać w opcjach.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 0, botów: 1

Robot: CCBot